feat: create reusable UI components
- Add Header component with navigation and auth state - Add Footer component with site information - Add BIIcon component for Bootstrap Italia icons - Add MemberDot component for member visualization - Add default layout wrapper Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
name: string
|
||||||
|
size?: 'xs' | 'sm' | 'lg' | 'xl'
|
||||||
|
color?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'white'
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
class="icon"
|
||||||
|
:class="[
|
||||||
|
size ? `icon-${size}` : '',
|
||||||
|
color ? `icon-${color}` : ''
|
||||||
|
]"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use :href="`/sprites.svg#${name}`"></use>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import MemberDot from "~/components/MemberDot.vue";
|
||||||
|
import { useMembers } from "~/composables/useMembers";
|
||||||
|
|
||||||
|
const { sortedMembers } = useMembers();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<footer class="it-footer">
|
||||||
|
<div class="it-footer-main">
|
||||||
|
<div class="container">
|
||||||
|
<section>
|
||||||
|
<div class="row clearfix">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="it-brand-wrapper">
|
||||||
|
<a href="#" data-focus-mouse="false">
|
||||||
|
<svg class="icon"><use xlink:href="https://italia.github.io/bootstrap-italia/dist/svg/sprites.svg#it-code-circle"></use></svg>
|
||||||
|
<div class="it-brand-text">
|
||||||
|
<h2>Visione</h2>
|
||||||
|
<h3 class="d-none d-md-block">Segnalazioni anonime e sicure</h3>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="py-4 border-white border-top">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 pb-2">
|
||||||
|
<div class="pb-2">
|
||||||
|
<h4>Progetto di</h4>
|
||||||
|
<div class="members-list d-flex justify-content-center align-items-center gap-5 flex-wrap w-100">
|
||||||
|
<MemberDot
|
||||||
|
v-for="member in sortedMembers"
|
||||||
|
:key="member.name"
|
||||||
|
:name="member.name"
|
||||||
|
:image="member.image"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="it-footer-small-prints clearfix">
|
||||||
|
<div class="container">
|
||||||
|
<ul class="it-footer-small-prints-list list-inline mb-0 d-flex flex-column flex-md-row">
|
||||||
|
<li class="list-inline-item"><a href="#">Note legali</a></li>
|
||||||
|
<li class="list-inline-item"><a href="#">Privacy policy</a></li>
|
||||||
|
<li class="list-inline-item"><a href="/sitemap.xml">Mappa del sito</a></li>
|
||||||
|
<li class="list-inline-item"><a href="https://form.agid.gov.it/view/xyz">Dichiarazione di accessibilità <span class="visually-hidden">(link esterno su sito AgID)</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const isMobileMenuOpen = ref(false)
|
||||||
|
|
||||||
|
watch(() => route.path, () => {
|
||||||
|
isMobileMenuOpen.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
const navLinks = [
|
||||||
|
{ name: 'Home', path: '/' },
|
||||||
|
{ name: 'Fai una segnalazione', path: '/reports/create' },
|
||||||
|
{ name: 'Esplora i dati', path: '/open-data' },
|
||||||
|
{ name: 'Come funziona', path: '/info' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const isAuthenticated = ref(false) // Replace with: const token = useCookie('auth_token'); computed(() => !!token.value)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header class="it-header-wrapper">
|
||||||
|
<div class="it-header-slim-wrapper">
|
||||||
|
<div class="container-xxl">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="it-header-slim-wrapper-content">
|
||||||
|
<a class="d-none d-lg-block navbar-brand" href="#">Dipartimento della Pubblica Sicurezza</a>
|
||||||
|
<div class="nav-mobile">
|
||||||
|
<nav aria-label="Navigazione secondaria">
|
||||||
|
<a class="it-opener d-lg-none" data-bs-toggle="collapse" href="#menuC2" role="button" aria-expanded="false" aria-controls="menuC2">
|
||||||
|
<span>Dipartmento della Pubblica Sicureazza</span>
|
||||||
|
<BIIcon name="it-expand" />
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="it-header-slim-right-zone">
|
||||||
|
<div class="it-access-top-wrapper">
|
||||||
|
<!-- Dynamic Login / Area Personale Button -->
|
||||||
|
<NuxtLink v-if="!isAuthenticated" class="btn btn-primary btn-sm" to="/auth/login">
|
||||||
|
Accedi
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink v-else class="btn btn-primary btn-sm" to="/dashboard">
|
||||||
|
Area Personale
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CENTER HEADER (Brand) -->
|
||||||
|
<div class="it-nav-wrapper">
|
||||||
|
<div class="it-header-center-wrapper">
|
||||||
|
<div class="container-xxl">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="it-header-center-content-wrapper">
|
||||||
|
<div class="it-brand-wrapper">
|
||||||
|
<NuxtLink to="/">
|
||||||
|
<BIIcon name="it-pa" />
|
||||||
|
<div class="it-brand-text">
|
||||||
|
<div class="it-brand-title">Visione</div>
|
||||||
|
<div class="it-brand-tagline d-none d-md-block">Un'occhio per la sicurezza del cittadino.</div>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="it-header-navbar-wrapper">
|
||||||
|
<div class="container-xxl">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<nav class="navbar navbar-expand-lg has-megamenu" aria-label="Navigazione principale">
|
||||||
|
<button
|
||||||
|
class="custom-navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
aria-label="Mostra la navigazione"
|
||||||
|
@click="isMobileMenuOpen = true"
|
||||||
|
>
|
||||||
|
<BIIcon name="it-burger" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="overlay"
|
||||||
|
:class="{ 'show': isMobileMenuOpen }"
|
||||||
|
@click="isMobileMenuOpen = false"
|
||||||
|
style="display: block;"
|
||||||
|
v-show="isMobileMenuOpen"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="navbar-collapsable"
|
||||||
|
:class="{ 'expanded': isMobileMenuOpen }"
|
||||||
|
style="display: block;"
|
||||||
|
>
|
||||||
|
<div class="close-div">
|
||||||
|
<button class="btn close-menu" type="button" @click="isMobileMenuOpen = false">
|
||||||
|
<span class="visually-hidden">Nascondi la navigazione</span>
|
||||||
|
<BIIcon name="it-close-big" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="menu-wrapper">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li
|
||||||
|
v-for="link in navLinks"
|
||||||
|
:key="link.path"
|
||||||
|
class="nav-item"
|
||||||
|
:class="{ 'active': route.path === link.path }"
|
||||||
|
>
|
||||||
|
<NuxtLink
|
||||||
|
class="nav-link"
|
||||||
|
:class="{ 'active': route.path === link.path }"
|
||||||
|
:to="link.path"
|
||||||
|
:aria-current="route.path === link.path ? 'page' : undefined"
|
||||||
|
>
|
||||||
|
<span>{{ link.name }}</span>
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.overlay {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.overlay.show {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
name?: string
|
||||||
|
image?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="member-dot">
|
||||||
|
<img :src="image" :alt="name" class="member-avatar">
|
||||||
|
<div class="member-name">{{ name }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.member-dot {
|
||||||
|
width: 96px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-avatar {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
background: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-name {
|
||||||
|
font-size: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Header from "~/components/Header.vue";
|
||||||
|
import Footer from "~/components/Footer.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="app-layout">
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<main class="app-main">
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user