Merge pull request 'Ajout d'un système de pages pour la barre de navigation' (#42) from feat/8_implement_main_ui_part2 into main

Cette PR explore une organisation en "pages" pour le rendu des différents "onglets" de la navbar.

Contrairement à la PR précédente (#40), les composants ajoutés viennent de la bibliothèque Flowbite

En plus des pages, cette PR :
- remplace le texte de chargement Chargement... par des skeleton (sorte de placeholder visuels)
- utilise le système HTMX de hx-swap-oob pour changer le titre dynamiquement
- ajoutent les fichiers minifiés de de htmx / alpinejs / flowbite dans les assets, avec leur numéro de version

Reviewed-on: #42
Reviewed-by: kosssi <simon@p4pillon.org>
Reviewed-by: theo <theo.lettermann@gmail.com>
This commit is contained in:
Florian Briand 2024-08-06 21:50:20 +02:00
commit b10fc30984
19 changed files with 402 additions and 18 deletions

File diff suppressed because one or more lines are too long

View File

@ -615,6 +615,30 @@ video {
margin-top: 0.75rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.mt-4 {
margin-top: 1rem;
}
.mt-6 {
margin-top: 1.5rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
.mb-2\.5 {
margin-bottom: 0.625rem;
}
.me-3 {
margin-inline-end: 0.75rem;
}
.block {
display: block;
}
@ -627,6 +651,10 @@ video {
display: inline-flex;
}
.grid {
display: grid;
}
.hidden {
display: none;
}
@ -651,6 +679,38 @@ video {
height: 100%;
}
.h-32 {
height: 8rem;
}
.h-48 {
height: 12rem;
}
.h-96 {
height: 24rem;
}
.h-2\.5 {
height: 0.625rem;
}
.h-9 {
height: 2.25rem;
}
.h-7 {
height: 1.75rem;
}
.h-2 {
height: 0.5rem;
}
.h-4 {
height: 1rem;
}
.min-h-full {
min-height: 100%;
}
@ -675,6 +735,22 @@ video {
width: auto;
}
.w-32 {
width: 8rem;
}
.w-20 {
width: 5rem;
}
.w-24 {
width: 6rem;
}
.w-28 {
width: 7rem;
}
.max-w-7xl {
max-width: 80rem;
}
@ -683,6 +759,10 @@ video {
max-width: 20rem;
}
.max-w-sm {
max-width: 24rem;
}
.flex-shrink-0 {
flex-shrink: 0;
}
@ -691,6 +771,24 @@ video {
transform-origin: top right;
}
@keyframes pulse {
50% {
opacity: .5;
}
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.items-center {
align-items: center;
}
@ -703,6 +801,10 @@ video {
justify-content: space-between;
}
.gap-4 {
gap: 1rem;
}
.space-y-1 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
@ -717,6 +819,22 @@ video {
border-radius: 0.375rem;
}
.rounded-lg {
border-radius: 0.5rem;
}
.rounded {
border-radius: 0.25rem;
}
.border-2 {
border-width: 2px;
}
.border {
border-width: 1px;
}
.border-b {
border-bottom-width: 1px;
}
@ -725,16 +843,35 @@ video {
border-top-width: 1px;
}
.border-dashed {
border-style: dashed;
}
.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
}
.border-gray-300 {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.bg-gray-200 {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
}
.bg-gray-300 {
--tw-bg-opacity: 1;
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
}
.p-1 {
padding: 0.25rem;
}
@ -743,6 +880,10 @@ video {
padding: 0.5rem;
}
.p-4 {
padding: 1rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
@ -826,12 +967,27 @@ video {
color: rgb(17 24 39 / var(--tw-text-opacity));
}
.text-gray-200 {
--tw-text-opacity: 1;
color: rgb(229 231 235 / var(--tw-text-opacity));
}
.opacity-0 {
opacity: 0;
}
.shadow-lg {
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.ring-1 {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
@ -895,6 +1051,10 @@ video {
display: none;
}
.sm\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.sm\:items-center {
align-items: center;
}
@ -911,6 +1071,20 @@ video {
}
}
@media (min-width: 768px) {
.md\:h-64 {
height: 16rem;
}
.md\:h-72 {
height: 18rem;
}
.md\:p-6 {
padding: 1.5rem;
}
}
@media (min-width: 1024px) {
.lg\:block {
display: block;
@ -920,8 +1094,39 @@ video {
display: none;
}
.lg\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.lg\:px-8 {
padding-left: 2rem;
padding-right: 2rem;
}
}
@media (prefers-color-scheme: dark) {
.dark\:border-gray-600 {
--tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity));
}
.dark\:border-gray-700 {
--tw-border-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-border-opacity));
}
.dark\:bg-gray-700 {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
}
.dark\:text-gray-600 {
--tw-text-opacity: 1;
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.dark\:text-gray-700 {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,4 @@
mod pages;
mod templates;
use std::path::Path;
@ -23,6 +24,7 @@ pub fn get_router(assets_path: &Path) -> axum::Router {
axum::Router::new()
.nest_service("/assets", ServeDir::new(assets_path))
.route("/", axum::routing::get(root))
.nest("/pages", pages::get_routes())
.merge(templates::get_routes())
.fallback(fallback)
}

View File

@ -0,0 +1,10 @@
use askama::Template;
use askama_axum::IntoResponse;
#[derive(Template)]
#[template(path = "pages/cps.html")]
struct CpsResponse;
pub async fn cps() -> impl IntoResponse {
CpsResponse.into_response()
}

View File

@ -0,0 +1,10 @@
use askama::Template;
use askama_axum::IntoResponse;
#[derive(Template)]
#[template(path = "pages/home.html")]
struct HomeResponse;
pub async fn home() -> impl IntoResponse {
HomeResponse.into_response()
}

View File

@ -0,0 +1,10 @@
use axum::{routing, Router};
mod cps;
mod home;
pub fn get_routes() -> Router {
Router::new()
.route("/home", routing::get(home::home))
.route("/cps", routing::get(cps::cps))
}

View File

@ -5,7 +5,7 @@ use serde::Deserialize;
struct MenuItem {
label: String,
id: String,
href: String,
current: bool,
}
@ -42,17 +42,12 @@ async fn menu(Query(params): Query<MenuParameters>) -> impl IntoResponse {
items: vec![
MenuItem {
label: "Accueil".to_string(),
id: "home".to_string(),
href: "/pages/home".to_string(),
current: true,
},
MenuItem {
label: "À propos".to_string(),
id: "about".to_string(),
current: false,
},
MenuItem {
label: "Contact".to_string(),
id: "contact".to_string(),
label: "CPS".to_string(),
href: "/pages/cps".to_string(),
current: false,
},
],

View File

@ -3,9 +3,11 @@
<head>
<title>{% block title %}{{ title }}{% endblock %}</title>
<script src="/assets/js/htmx.min.js"></script>
<script src="//unpkg.com/alpinejs" defer></script>
<script src="/assets/js/htmx@2.0.1.min.js"></script>
<script src="/assets/js/alpinejs@3.14.1.min.js" defer></script>
<link href="/assets/css/style.css" rel="stylesheet">
<link href="/assets/css/flowbite@2.5.1.min.css" rel="stylesheet" />
<script src="/assets/js/flowbite@2.5.1.min.js"></script>
{% block head %}{% endblock %}
</head>

View File

@ -6,20 +6,27 @@
<div class="py-10">
<header>
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">Dashboard</h1>
<h1
id="page-title"
class="text-3xl font-bold leading-tight tracking-tight text-gray-900"
>
{% include "skeletons/page-title.html" %}
</h1>
</div>
</header>
<main>
<div class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<div
id="main-container"
class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"
>
<!-- Your content -->
<div
id="hello"
hx-get="/hello"
hx-get="/pages/home"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
>
Chargement ...
{% include "skeletons/card.html" %}
</div>
</div>
</main>

View File

@ -5,5 +5,5 @@
hx-trigger="load"
hx-swap="outerHTML"
>
Chargement ...
{% include "skeletons/menu-items.html" %}
</div>

View File

@ -1,6 +1,10 @@
{% for item in items %}
<a
href="#{{ item.id }}"
href=""
hx-get="{{ item.href }}"
hx-trigger="click"
hx-target="#main-container"
hx-swap="innerHTML"
class="{{ Self::get_classes(self, item.current) }}"
aria-current="{% if item.current %}page{% endif %}"
>

View File

@ -0,0 +1,52 @@
<h3 id="page-title" hx-swap-oob="textContent">
CPS
</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
<div
class="border-2 border-dashed border-gray-300 rounded-lg dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
></div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
></div>
<div class="grid grid-cols-2 gap-4">
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
</div>

View File

@ -0,0 +1,52 @@
<h3 id="page-title" hx-swap-oob="textContent">
Accueil
</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
<div
class="border-2 border-dashed border-gray-300 rounded-lg dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
></div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
></div>
<div class="grid grid-cols-2 gap-4">
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
</div>

View File

@ -0,0 +1,22 @@
<div role="status" class="animate-pulse max-w-sm p-4 border border-gray-200 rounded shadow md:p-6 dark:border-gray-700">
<div class="flex items-center justify-center h-48 mb-4 bg-gray-300 rounded dark:bg-gray-700">
<svg class="w-10 h-10 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 20">
<path d="M14.066 0H7v5a2 2 0 0 1-2 2H0v11a1.97 1.97 0 0 0 1.934 2h12.132A1.97 1.97 0 0 0 16 18V2a1.97 1.97 0 0 0-1.934-2ZM10.5 6a1.5 1.5 0 1 1 0 2.999A1.5 1.5 0 0 1 10.5 6Zm2.221 10.515a1 1 0 0 1-.858.485h-8a1 1 0 0 1-.9-1.43L5.6 10.039a.978.978 0 0 1 .936-.57 1 1 0 0 1 .9.632l1.181 2.981.541-1a.945.945 0 0 1 .883-.522 1 1 0 0 1 .879.529l1.832 3.438a1 1 0 0 1-.031.988Z"/>
<path d="M5 5V.13a2.96 2.96 0 0 0-1.293.749L.879 3.707A2.98 2.98 0 0 0 .13 5H5Z"/>
</svg>
</div>
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
<div class="flex items-center mt-4">
<svg class="w-10 h-10 me-3 text-gray-200 dark:text-gray-700" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z"/>
</svg>
<div>
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-32 mb-2"></div>
<div class="w-48 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
</div>
</div>
<span class="sr-only">Loading...</span>
</div>

View File

@ -0,0 +1,4 @@
<div role="status" class="animate-pulse flex items-center justify-center h-full">
<div class="w-32 h-4 bg-gray-200 rounded-full dark:bg-gray-700 me-3"></div>
<div class="w-32 h-4 bg-gray-200 rounded-full dark:bg-gray-700"></div>
</div>

View File

@ -0,0 +1 @@
<div role="status" class="animate-pulse h-7 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mt-3"></div>