feat: make Nav and Profile menu dynamic

This commit is contained in:
Florian Briand 2024-08-05 00:15:28 +02:00
parent 84cd97bc5c
commit d320bbcc1d
Signed by: florian_briand
GPG Key ID: CC981B9E6B98E70B
13 changed files with 226 additions and 240 deletions

1
Cargo.lock generated
View File

@ -69,6 +69,7 @@ dependencies = [
"askama",
"askama_axum",
"axum",
"serde",
"tokio",
"tower-http",
]

View File

@ -7,6 +7,7 @@ edition = "2021"
askama = "0.12.1"
askama_axum = "0.4.0"
axum = "0.7.5"
serde = { version = "1.0.204", features = ["derive"] }
tokio = { version = "1.39.1", features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.5.2", features = ["fs"] }

View File

@ -566,10 +566,6 @@ video {
border-width: 0;
}
.visible {
visibility: visible;
}
.absolute {
position: absolute;
}
@ -586,15 +582,6 @@ video {
inset: -0.375rem;
}
.inset-y-0 {
top: 0px;
bottom: 0px;
}
.left-0 {
left: 0px;
}
.right-0 {
right: 0px;
}
@ -608,22 +595,22 @@ video {
margin-right: auto;
}
.ml-3 {
margin-left: 0.75rem;
}
.mt-2 {
margin-top: 0.5rem;
}
.-mr-2 {
margin-right: -0.5rem;
}
.ml-3 {
margin-left: 0.75rem;
}
.ml-auto {
margin-left: auto;
}
.mt-2 {
margin-top: 0.5rem;
}
.mt-3 {
margin-top: 0.75rem;
}
@ -644,6 +631,10 @@ video {
display: none;
}
.h-10 {
height: 2.5rem;
}
.h-16 {
height: 4rem;
}
@ -660,14 +651,14 @@ video {
height: 100%;
}
.h-10 {
height: 2.5rem;
}
.min-h-full {
min-height: 100%;
}
.w-10 {
width: 2.5rem;
}
.w-48 {
width: 12rem;
}
@ -684,10 +675,6 @@ video {
width: auto;
}
.w-10 {
width: 2.5rem;
}
.max-w-7xl {
max-width: 80rem;
}
@ -696,10 +683,6 @@ video {
max-width: 20rem;
}
.flex-1 {
flex: 1 1 0%;
}
.flex-shrink-0 {
flex-shrink: 0;
}
@ -708,32 +691,6 @@ video {
transform-origin: top right;
}
.-translate-y-full {
--tw-translate-y: -100%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-y-0 {
--tw-translate-y: 0px;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.scale-100 {
--tw-scale-x: 1;
--tw-scale-y: 1;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.scale-95 {
--tw-scale-x: .95;
--tw-scale-y: .95;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.items-center {
align-items: center;
}
@ -760,6 +717,10 @@ video {
border-radius: 0.375rem;
}
.border-b {
border-bottom-width: 1px;
}
.border-b-2 {
border-bottom-width: 2px;
}
@ -768,14 +729,15 @@ video {
border-left-width: 4px;
}
.border-b {
border-bottom-width: 1px;
}
.border-t {
border-top-width: 1px;
}
.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
}
.border-indigo-500 {
--tw-border-opacity: 1;
border-color: rgb(99 102 241 / var(--tw-border-opacity));
@ -785,11 +747,6 @@ video {
border-color: transparent;
}
.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
}
.bg-gray-100 {
--tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
@ -818,11 +775,6 @@ video {
padding-right: 0.25rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
@ -848,18 +800,14 @@ video {
padding-bottom: 2rem;
}
.pb-4 {
padding-bottom: 1rem;
.pb-3 {
padding-bottom: 0.75rem;
}
.pl-3 {
padding-left: 0.75rem;
}
.pr-2 {
padding-right: 0.5rem;
}
.pr-4 {
padding-right: 1rem;
}
@ -872,10 +820,6 @@ video {
padding-top: 0.5rem;
}
.pb-3 {
padding-bottom: 0.75rem;
}
.pt-4 {
padding-top: 1rem;
}
@ -921,11 +865,21 @@ video {
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.text-gray-600 {
--tw-text-opacity: 1;
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.text-gray-700 {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
}
.text-gray-800 {
--tw-text-opacity: 1;
color: rgb(31 41 55 / var(--tw-text-opacity));
}
.text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity));
@ -936,30 +890,6 @@ video {
color: rgb(67 56 202 / var(--tw-text-opacity));
}
.text-gray-600 {
--tw-text-opacity: 1;
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.text-gray-800 {
--tw-text-opacity: 1;
color: rgb(31 41 55 / var(--tw-text-opacity));
}
.opacity-0 {
opacity: 0;
}
.opacity-100 {
opacity: 1;
}
.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);
}
.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);
@ -981,34 +911,6 @@ video {
--tw-ring-opacity: 0.05;
}
.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.duration-200 {
transition-duration: 200ms;
}
.duration-75 {
transition-duration: 75ms;
}
.duration-300 {
transition-duration: 300ms;
}
.ease-in {
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
}
.ease-out {
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
.hover\:border-gray-300:hover {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
@ -1050,10 +952,6 @@ video {
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.focus\:ring-inset:focus {
--tw-ring-inset: inset;
}
.focus\:ring-indigo-500:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity));
@ -1064,14 +962,6 @@ video {
}
@media (min-width: 640px) {
.sm\:static {
position: static;
}
.sm\:inset-auto {
inset: auto;
}
.sm\:-my-px {
margin-top: -1px;
margin-bottom: -1px;
@ -1093,14 +983,6 @@ video {
align-items: center;
}
.sm\:items-stretch {
align-items: stretch;
}
.sm\:justify-start {
justify-content: flex-start;
}
.sm\:space-x-8 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(2rem * var(--tw-space-x-reverse));
@ -1111,10 +993,6 @@ video {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.sm\:pr-0 {
padding-right: 0px;
}
}
@media (min-width: 1024px) {

View File

@ -1,8 +1,12 @@
use axum::Router;
mod hello;
mod nav;
mod profile;
pub fn get_routes() -> Router {
Router::new()
.nest("/hello", hello::get_routes())
.nest("/nav", nav::get_routes())
.nest("/profile", profile::get_routes())
}

View File

@ -0,0 +1,65 @@
use askama::Template;
use askama_axum::IntoResponse;
use axum::{extract::Query, routing, Router};
use serde::Deserialize;
struct MenuItem {
label: String,
id: String,
current: bool,
}
#[derive(Deserialize)]
struct MenuParameters {
mobile: bool,
}
#[derive(Template)]
#[template(path = "layout/nav/nav-menu-items.html")]
struct MenuResponse {
mobile: bool,
items: Vec<MenuItem>,
}
impl MenuResponse {
fn get_classes(&self, is_current_item: &bool) -> String {
let common_classes = match self.mobile {
true => "block border-l-4 py-2 pl-3 pr-4 text-base font-medium".to_string(),
false => "inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium".to_string(),
};
match (self.mobile, is_current_item) {
(true, true) => common_classes + " border-indigo-500 bg-indigo-50 text-indigo-700",
(true, false) => common_classes + " border-transparent text-gray-600 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-800",
(false, true) => common_classes + " border-indigo-500 text-gray-900",
(false, false) => common_classes + " border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
}
}
}
async fn menu(Query(params): Query<MenuParameters>) -> impl IntoResponse {
MenuResponse {
mobile: params.mobile,
items: vec![
MenuItem {
label: "Accueil".to_string(),
id: "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(),
current: false,
},
],
}.into_response()
}
pub fn get_routes() -> Router {
Router::new()
.route("/menu", routing::get(menu))
}

View File

@ -0,0 +1,65 @@
use askama::Template;
use askama_axum::IntoResponse;
use axum::{extract::Query, routing, Router};
use serde::Deserialize;
struct MenuItem {
label: String,
id: String,
current: bool,
}
#[derive(Deserialize)]
struct MenuParameters {
mobile: bool,
}
#[derive(Template)]
#[template(path = "layout/nav/profile-menu-items.html")]
struct MenuResponse {
mobile: bool,
items: Vec<MenuItem>,
}
impl MenuResponse {
fn get_classes(&self, is_current_item: &bool) -> String {
let common_classes = match self.mobile {
true => "block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800".to_string(),
false => "block px-4 py-2 text-sm text-gray-700".to_string(),
};
match (self.mobile, is_current_item) {
(true, true) => common_classes + "", // ???
(true, false) => common_classes + "",
(false, true) => common_classes + " bg-gray-100",
(false, false) => common_classes + "",
}
}
}
async fn menu(Query(params): Query<MenuParameters>) -> impl IntoResponse {
MenuResponse {
mobile: params.mobile,
items: vec![
MenuItem {
label: "Votre profil".to_string(),
id: "profile".to_string(),
current: false,
},
MenuItem {
label: "Paramètres".to_string(),
id: "settings".to_string(),
current: false,
},
MenuItem {
label: "Déconnexion".to_string(),
id: "logout".to_string(),
current: false,
},
],
}.into_response()
}
pub fn get_routes() -> Router {
Router::new()
.route("/menu", routing::get(menu))
}

View File

@ -19,7 +19,7 @@
hx-trigger="load"
hx-swap="outerHTML"
>
Loading...
Chargement ...
</div>
</div>
</main>

View File

@ -1,22 +1,9 @@
<!-- Current: "border-indigo-500 text-gray-900", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" -->
<a
href="#"
class="inline-flex items-center border-b-2 border-indigo-500 px-1 pt-1 text-sm font-medium text-gray-900"
aria-current="page"
>Dashboard</a
>
<a
href="#"
class="inline-flex items-center border-b-2 border-transparent px-1 pt-1 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700"
>Team</a
>
<a
href="#"
class="inline-flex items-center border-b-2 border-transparent px-1 pt-1 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700"
>Projects</a
>
<a
href="#"
class="inline-flex items-center border-b-2 border-transparent px-1 pt-1 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700"
>Calendar</a
<div
id="nav-menu-desktop"
hx-get="/nav/menu?mobile=false"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
>
Chargement ...
</div>

View File

@ -10,29 +10,13 @@
x-cloak
x-transition
>
<!-- Active: "bg-gray-100", Not Active: "" -->
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
role="menuitem"
tabindex="-1"
id="user-menu-item-0"
>Your Profile</a
>
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
role="menuitem"
tabindex="-1"
id="user-menu-item-1"
>Settings</a
>
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
role="menuitem"
tabindex="-1"
id="user-menu-item-2"
>Sign out</a
<div
id="profile-menu-desktop"
hx-get="/profile/menu?mobile=false"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
>
Chargement ...
</div>
</div>

View File

@ -1,22 +1,9 @@
<!-- Current: "border-indigo-500 bg-indigo-50 text-indigo-700", Default: "border-transparent text-gray-600 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-800" -->
<a
href="#"
class="block border-l-4 border-indigo-500 bg-indigo-50 py-2 pl-3 pr-4 text-base font-medium text-indigo-700"
aria-current="page"
>Dashboard</a
>
<a
href="#"
class="block border-l-4 border-transparent py-2 pl-3 pr-4 text-base font-medium text-gray-600 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-800"
>Team</a
>
<a
href="#"
class="block border-l-4 border-transparent py-2 pl-3 pr-4 text-base font-medium text-gray-600 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-800"
>Projects</a
>
<a
href="#"
class="block border-l-4 border-transparent py-2 pl-3 pr-4 text-base font-medium text-gray-600 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-800"
>Calendar</a
<div
id="nav-menu-mobile"
hx-get="/nav/menu?mobile=true"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
>
Chargement ...
</div>

View File

@ -1,15 +1,9 @@
<a
href="#"
class="block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800"
>Your Profile</a
>
<a
href="#"
class="block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800"
>Settings</a
>
<a
href="#"
class="block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800"
>Sign out</a
<div
id="profile-menu-mobile"
hx-get="/profile/menu?mobile=true"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
>
Chargement ...
</div>

View File

@ -0,0 +1,9 @@
{% for item in items %}
<a
href="#{{ item.id }}"
class="{{ Self::get_classes(self, item.current) }}"
aria-current="{% if item.current %}page{% endif %}"
>
{{ item.label }}
</a>
{% endfor %}

View File

@ -0,0 +1,11 @@
{% for item in items %}
<a
href="#{{ item.id }}"
class="{{ Self::get_classes(self, item.current) }}"
role="menuitem"
tabindex="-1"
id="{{ item.id }}"
>
{{ item.label }}
</a>
{% endfor %}