14.

Tailwind CSS

|

Share this awesome video!

|

¿Qué pasa con el CSS? Eres libre de añadir el CSS que quieras a app/styles/app.css. Ese archivo ya está cargado en la página.

¿Quieres utilizar CSS de Bootstrap? Consulta la documentación de Asset Mapper sobre cómo hacerlo. O, si quieres usar Sass, hay un symfonycasts/sass-bundle que te lo pone fácil. No obstante, te recomiendo que no te lances a usar Sass demasiado rápido, ya que muchas de las funciones por las que Sass es famoso pueden hacerse ahora en CSS nativo, como las variables CSS e incluso el anidamiento CSS.

Hola Tailwind

¿Cuál es mi elección personal para un framework CSS? Tailwind. Y parte de la razón es que Tailwind es increíblemente popular. Así que si buscas recursos o componentes preconstruidos, vas a tener mucha suerte si utilizas Tailwind.

Pero Tailwind es un poco extraño en un sentido: no es simplemente un gran archivo CSS que pones en tu página. En su lugar, tiene un proceso de construcción que escanea tu código en busca de todas las clases Tailwind que estés utilizando. Luego vuelca un archivo CSS final que sólo contiene el código que necesitas.

En el mundo Symfony, si quieres utilizar Tailwind, hay un bundle que lo hace realmente fácil. Gira tu terminal e instala un nuevo paquete: composer require symfonycasts - hey los conozco - tailwind-bundle:

composer require symfonycasts/tailwind-bundle

Para este paquete, la receta no hace nada más que activar el nuevo bundle. Para poner en marcha Tailwind, una vez en tu proyecto, ejecuta:

php bin/console tailwind:init

Esto hace tres cosas. En primer lugar, descarga un binario de Tailwind en segundo plano, algo en lo que nunca tendrás que pensar. En segundo lugar, crea un archivo tailwind.config.jsen la raíz de nuestro proyecto. Esto indica a Tailwind dónde tiene que buscar en nuestro proyecto las clases CSS de Tailwind. Y tercero, actualiza nuestro app.css para añadir estas tres líneas. Éstas serán sustituidas por el código real de Tailwind en segundo plano por el binario.

Ejecutar Tailwind

Por último, hay que compilar Tailwind, así que tenemos que ejecutar un comando para hacerlo:

php bin/console tailwind:build -w

Esto escanea nuestras plantillas y genera el archivo CSS final en segundo plano. El -w lo pone en modo "vigilar": en lugar de construir una vez y salir, vigila nuestras plantillas en busca de cambios. Cuando detecte alguna actualización, reconstruirá automáticamente el archivo CSS. Lo veremos en un minuto.

Pero ya deberíamos ver una diferencia. Vamos a la página de inicio. ¿Lo has visto? El código base de Tailwind ha hecho un reinicio. Por ejemplo, ¡nuestro h1 es ahora diminuto!

Ver Tailwind en acción

Probemos esto de verdad. Abre templates/main/homepage.html.twig. Encima de h1, hazlo más grande añadiendo una clase: text-2xl.

48 lines | templates/main/homepage.html.twig
// ... lines 1 - 4
{% block body %}
<h1 class="text-2xl">
Starshop: your monopoly-busting option for Starship parts!
</h1>
// ... lines 9 - 46
{% endblock %}

En cuanto guardemos eso, podrás ver que tailwind se dio cuenta de nuestro cambio y reconstruyó el CSS. Y cuando actualizamos, ¡se hizo más grande!

Nuestro archivo fuente app.css sigue siendo super sencillo: sólo esas pocas líneas que vimos antes. Pero mira el código fuente de la página y abre el app.css que se está enviando a nuestros usuarios. ¡Es la versión construida de Tailwind! Entre bastidores, existe cierta magia que sustituye esas tres líneas de Tailwind por el código CSS real de Tailwind.

Ejecutar automáticamente Tailwind con el binario symfony

Y... ¡eso es todo! Simplemente funciona. Aunque hay una forma más fácil y automática de ejecutar Tailwind. Pulsa Ctrl+C en el comando Tailwind para detenerlo. A continuación, en la raíz de nuestro proyecto, crea un archivo llamado .symfony.local.yaml. Se trata de un archivo de configuración para el servidor web binario symfony que estamos utilizando. Dentro, añade workers, tailwind, y luego cmd configurados en una matriz con cada parte de un comando: symfony, console, tailwind, build, --watch, o podrías utilizar -w: es lo mismo.

Aún no he hablado de ello, pero en lugar de ejecutar php bin/console, también podemos ejecutar symfony console seguido de cualquier comando para obtener el mismo resultado. Hablaremos de por qué te conviene hacerlo en un futuro tutorial. Pero por ahora, considera que bin/console y symfony console son lo mismo.

Además, al añadir esta clave workers, significa que en lugar de que tengamos que ejecutar el comando manualmente, cuando iniciemos el servidor web symfony, éste lo ejecutará por nosotros en segundo plano.

Observa. En tu primera pestaña, pulsa Ctrl+C para detener el servidor web... luego vuelve a ejecutar

symfony serve

para que vea el nuevo archivo de configuración. Mira: ¡ahí está! ¡Está ejecutando el comando tailwind en segundo plano!

Podemos aprovecharnos de esto inmediatamente. En homepage.html.twig, cambia esto atext-4xl, gira y... ¡funciona! Ya ni siquiera tenemos que pensar en el comandotailwind:build.

48 lines | templates/main/homepage.html.twig
// ... lines 1 - 4
{% block body %}
<h1 class="text-4xl">
Starshop: your monopoly-busting option for Starship parts!
</h1>
// ... lines 9 - 46
{% endblock %}

Y como estilizaremos con Tailwind, elimina el fondo azul.

Copiar en plantillas estilizadas

Vale, este tutorial no trata sobre Tailwind ni sobre cómo diseñar un sitio web. Créeme, no quieres que Ryan dirija la carga del diseño web. Pero sí quiero tener un sitio bonito... y también es importante pasar por el proceso de trabajar con un diseñador.

Así que imaginemos que otra persona ha creado un diseño para nuestro sitio. E incluso nos han dado algo de HTML con clases de Tailwind para ese diseño. Si descargas el código del curso, en un directorio de tutorial/templates/, tenemos 3 plantillas. Uno a uno, voy a copiar cada archivo y pegarlo sobre el original. No te preocupes, veremos lo que ocurre en cada uno de estos archivos.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
{% block stylesheets %}
{% endblock %}
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
{% endblock %}
</head>
<body class="text-white" style="background: radial-gradient(102.21% 102.21% at 50% 28.75%, #00121C 42.62%, #013954 100%);">
<div class="flex flex-col justify-between min-h-screen relative">
<div>
<header class="h-[114px] shrink-0 flex flex-col sm:flex-row items-center sm:justify-between py-4 sm:py-0 px-6 border-b border-white/20 shadow-md">
<a href="#">
<img class="h-[42px]" src="{{ asset('images/starshop-logo.png') }}" alt="starshop logo">
</a>
<nav class="flex space-x-4 font-semibold">
<a class="hover:text-amber-400 pt-2" href="#">
Home
</a>
<a class="hover:text-amber-400 pt-2" href="#">
About
</a>
<a class="hover:text-amber-400 pt-2" href="#">
Contact
</a>
<a class="rounded-[60px] py-2 px-5 bg-white/10 hover:bg-white/20" href="#">
Get Started
</a>
</nav>
</header>
{% block body %}{% endblock %}
</div>
<div class="p-5 bg-white/5 mt-3 text-center">
Made with ❤️ by <a class="text-[#0086C4]" href="https://symfonycasts.com">SymfonyCasts</a>
</div>
</div>
</body>
</html>

Haz homepage.html.twig...

{% extends 'base.html.twig' %}
{% block title %}Starshop: Beam up some parts!{% endblock %}
{% block body %}
<main class="flex flex-col lg:flex-row">
<aside
class="pb-8 lg:pb-0 lg:w-[411px] shrink-0 lg:block lg:min-h-screen text-white transition-all overflow-hidden px-8 border-b lg:border-b-0 lg:border-r border-white/20"
>
<div class="flex justify-between mt-11 mb-7">
<h2 class="text-[32px] font-semibold">My Ship Status</h2>
<button>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 448 512"><!--!Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path fill="#fff" d="M384 96c0-17.7 14.3-32 32-32s32 14.3 32 32V416c0 17.7-14.3 32-32 32s-32-14.3-32-32V96zM9.4 278.6c-12.5-12.5-12.5-32.8 0-45.3l128-128c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3L109.3 224 288 224c17.7 0 32 14.3 32 32s-14.3 32-32 32l-178.7 0 73.4 73.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-128-128z"/></svg>
</button>
</div>
<div>
<div class="flex flex-col space-y-1.5">
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center" style="background: rgba(255, 184, 0, .1);">
<div class="rounded-full h-2 w-2 bg-amber-400 blur-[1px] mr-2"></div>
<p class="uppercase text-xs">in progress</p>
</div>
<h3 class="tracking-tight text-[22px] font-semibold">
<a class="hover:underline" href="{{ path('app_starship_show', {
id: myShip.id
}) }}">{{ myShip.name }}</a>
</h3>
</div>
<div class="flex mt-4">
<div class="border-r border-white/20 pr-8">
<p class="text-slate-400 text-xs">Captain</p>
<p class="text-xl">{{ myShip.captain }}</p>
</div>
<div class="pl-8">
<p class="text-slate-400 text-xs">Class</p>
<p class="text-xl">{{ myShip.class }}</p>
</div>
</div>
</div>
</aside>
<div class="px-12 pt-10 w-full">
<h1 class="text-4xl font-semibold mb-8">
Ship Repair Queue
</h1>
<div class="space-y-5">
<!-- start ship item -->
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between">
<div class="flex justify-center min-[1174px]:justify-start">
<img class="h-[83px] w-[84px]" src="/images/status-in-progress.png" alt="Status: in progress">
<div class="ml-5">
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center bg-amber-400/10">
<div class="rounded-full h-2 w-2 bg-amber-400 blur-[1px] mr-2"></div>
<p class="uppercase text-xs text-nowrap">in progress</p>
</div>
<h4 class="text-[22px] pt-1 font-semibold">
<a
class="hover:text-slate-200"
href="#"
>USS LeafyCruiser</a>
</h4>
</div>
</div>
<div class="flex justify-center min-[1174px]:justify-start mt-2 min-[1174px]:mt-0 shrink-0">
<div class="border-r border-white/20 pr-8">
<p class="text-slate-400 text-xs">Captain</p>
<p class="text-xl">Jean-Luc Pickles</p>
</div>
<div class="pl-8 w-[100px]">
<p class="text-slate-400 text-xs">Class</p>
<p class="text-xl">Garden</p>
</div>
</div>
</div>
<!-- end ship item -->
</div>
<p class="text-lg mt-5 text-center md:text-left">
Looking for your next galactic ride?
<a href="#" class="underline font-semibold">Browse the {{ ships|length * 10 }} starships for sale!</a>
</p>
</div>
</main>
{% endblock %}

y finalmente show.html.twig.

{% extends 'base.html.twig' %}
{% block title %}{{ ship.name }}{% endblock %}
{% block body %}
<div class="my-4 px-8">
<a class="bg-white hover:bg-gray-200 rounded-xl p-2 text-black" href="#">
<svg class="inline text-black" xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#000" d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>
Back
</a>
</div>
<div class="md:flex justify-center space-x-3 mt-5 px-4 lg:px-8">
<div class="flex justify-center">
<img class="max-h-[300px] md:max-h-[500px]" src="{{ asset('images/purple-rocket.png') }}" alt="purple ship launching">
</div>
<div class="space-y-5">
<div class="mt-8 max-w-xl mx-auto">
<div class="px-8 pt-8">
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center bg-amber-400/10">
<div class="rounded-full h-2 w-2 bg-amber-400 blur-[1px] mr-2"></div>
<p class="uppercase text-xs">{{ ship.status }}</p>
</div>
<h1 class="text-[32px] font-semibold border-b border-white/10 pb-5 mb-5">
{{ ship.name }}
</h1>
<h4 class="text-xs text-slate-300 font-semibold mt-2 uppercase">Spaceship Captain</h4>
<p class="text-[22px] font-semibold">{{ ship.captain }}</p>
<h4 class="text-xs text-slate-300 font-semibold mt-2 uppercase">Class</h4>
<p class="text-[22px] font-semibold">{{ ship.class }}</p>
<h4 class="text-xs text-slate-300 font-semibold mt-2 uppercase">Ship Status</h4>
<p class="text-[22px] font-semibold">30,000 lys to next service</p>
</div>
</div>
</div>
</div>
{% endblock %}

Tip

Si copias los archivos (en lugar del contenido de los archivos), puede que el sistema de caché de Symfony no note el cambio y no veas el nuevo diseño. Si eso ocurre, borra la caché ejecutando php bin/console cache:clear.

Voy a borrar por completo el directorio tutorial/ para no confundirme y editar las plantillas equivocadas.

Vale, ¡vamos a ver qué ha hecho esto! Actualizar. ¡Tiene un aspecto precioso! Me encanta trabajar dentro de un diseño bonito. Pero... algunas partes están rotas. En homepage.html.twig, ésta es nuestra cola de reparación de barcos... que queda muy bien... ¡pero no hay código Twig! El estado está codificado, el nombre está codificado y no hay bucle.

88 lines | templates/main/homepage.html.twig
// ... lines 1 - 4
{% block body %}
<main class="flex flex-col lg:flex-row">
// ... lines 7 - 42
<div class="px-12 pt-10 w-full">
<h1 class="text-4xl font-semibold mb-8">
Ship Repair Queue
</h1>
<div class="space-y-5">
<!-- start ship item -->
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between">
<div class="flex justify-center min-[1174px]:justify-start">
<img class="h-[83px] w-[84px]" src="/images/status-in-progress.png" alt="Status: in progress">
<div class="ml-5">
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center bg-amber-400/10">
<div class="rounded-full h-2 w-2 bg-amber-400 blur-[1px] mr-2"></div>
<p class="uppercase text-xs text-nowrap">in progress</p>
</div>
<h4 class="text-[22px] pt-1 font-semibold">
<a
class="hover:text-slate-200"
href="#"
>USS LeafyCruiser</a>
</h4>
</div>
</div>
<div class="flex justify-center min-[1174px]:justify-start mt-2 min-[1174px]:mt-0 shrink-0">
<div class="border-r border-white/20 pr-8">
<p class="text-slate-400 text-xs">Captain</p>
<p class="text-xl">Jean-Luc Pickles</p>
</div>
<div class="pl-8 w-[100px]">
<p class="text-slate-400 text-xs">Class</p>
<p class="text-xl">Garden</p>
</div>
</div>
</div>
<!-- end ship item -->
</div>
// ... lines 80 - 84
</div>
</main>
{% endblock %}

A continuación: tomemos nuestro nuevo diseño y hagámoslo dinámico. También aprenderemos a organizar las cosas en parciales de plantilla e introduciremos un enum PHP, que son divertidos.