Skip to content

Commit

Permalink
feat: 💄 add theme selection to website
Browse files Browse the repository at this point in the history
  • Loading branch information
nutfdt committed Jan 4, 2025
1 parent 4b2ca33 commit 66c2bf0
Show file tree
Hide file tree
Showing 27 changed files with 325 additions and 13 deletions.
64 changes: 62 additions & 2 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,74 @@ onBeforeUnmount(() => {
window.removeEventListener("offline", updateOnlineStatus);
});
onMounted(useScheme);
registerSW({ immediate: true });
const preferences = reactive<{
theme: "dark" | "light" | undefined;
scheme: "dark" | "light" | "system" | undefined;
}>({
theme: undefined,
scheme: undefined,
});
onMounted(() => {
const { theme, scheme, setScheme } = useScheme() as UseSchemeResult;
preferences.theme = theme.value as "dark" | "light";
preferences.scheme = scheme.value as "dark" | "light" | "system";
watchEffect(() => {
preferences.scheme = preferences.theme as "dark" | "light";
});
watchEffect(() =>
setScheme(preferences.scheme as "dark" | "light" | "system"),
);
});
const toggleTheme = () => {
if (preferences.theme === "dark") {
preferences.theme = "light";
} else {
preferences.theme = "dark";
}
};
const themeLabel = computed(() => {
if (preferences.theme === "dark") {
return "Thème clair";
}
return "Thème sombre";
});
const themeIcon = computed(() => {
if (preferences.theme === "dark") {
return "ri-moon-line";
}
return "ri-sun-line";
});
</script>

<template>
<HeaderMain v-show="online" />
<div v-show="online">
<AppHeader
v-model="searchQuery"
:logo-text="logoText"
:quick-links="quickLinks"
>
<template #after-quick-links>
<DsfrButton
type="button"
:label="themeLabel"
:icon="{ name: themeIcon, scale: 1 }"
@click="toggleTheme()"
/>
</template>
</AppHeader>
</div>
<router-view v-if="online" />
<div v-else id="app">
<HeaderMain />
<AppHeader />
<div class="text-center relative top-1/6 m-4">
<h1>Problème de connexion</h1>
<p>Vous n'avez pas accès à Internet.</p>
Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {};
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AppHeader: typeof import('./components/AppHeader.vue')['default']
AskingExpert: typeof import('./components/AskingExpert.vue')['default']
ContactExpert: typeof import('./components/ContactExpert.vue')['default']
DsfrAlert: typeof import('@gouvminint/vue-dsfr')['DsfrAlert']
Expand Down
251 changes: 251 additions & 0 deletions frontend/src/components/AppHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref, useSlots } from "vue";
import {
DsfrLogo,
DsfrSearchBar,
DsfrHeaderMenuLinks,
type DsfrHeaderProps,
} from "@gouvminint/vue-dsfr";
const props = withDefaults(defineProps<DsfrHeaderProps>(), {
serviceTitle: undefined,
serviceDescription: undefined,
logoText: ["Ministère", "de l’intérieur"],
homeTo: "/",
modelValue: "",
operatorImgAlt: "",
operatorImgSrc: "",
operatorImgStyle: () => ({}),
placeholder: "Rechercher...",
quickLinks: () => [
{
label: "Informations",
to: "/",
},
{
label: "À propos",
to: "/a-propos",
},
{
label: "Mentions légales",
to: "/mentions-legales",
},
{
label: "Contact",
to: "/contact",
},
{
label: "Accessibilité : partiellement conforme",
to: "/accessibilite",
},
],
searchLabel: "Recherche",
quickLinksAriaLabel: "Menu secondaire",
});
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
hideModal();
}
};
onMounted(() => {
document.addEventListener("keydown", onKeyDown);
});
onUnmounted(() => {
document.removeEventListener("keydown", onKeyDown);
});
const menuOpened = ref(false);
const searchModalOpened = ref(false);
const modalOpened = ref(false);
const hideModal = () => {
modalOpened.value = false;
menuOpened.value = false;
searchModalOpened.value = false;
document.getElementById("button-menu")?.focus();
};
const showMenu = () => {
modalOpened.value = true;
menuOpened.value = true;
searchModalOpened.value = false;
document.getElementById("close-button")?.focus();
};
const showSearchModal = () => {
modalOpened.value = true;
menuOpened.value = false;
searchModalOpened.value = true;
};
const onQuickLinkClick = hideModal;
const slots = useSlots();
const isWithSlotOperator = computed(
() => Boolean(slots.operator?.().length) || !!props.operatorImgSrc,
);
const isWithSlotNav = computed(() => Boolean(slots.mainnav));
// eslint-disable-next-line func-call-spacing
defineEmits<{
(e: "update:modelValue", payload: string): void;
(e: "search", payload: string): void;
}>();
</script>

<template>
<header role="banner" class="fr-header">
<div class="fr-header__body">
<div class="fr-container width-inherit">
<div class="fr-header__body-row">
<div class="fr-header__brand fr-enlarge-link">
<div class="fr-header__brand-top">
<div class="fr-header__logo">
<DsfrLogo :logo-text="logoText" data-testid="header-logo" />
</div>
<div v-if="isWithSlotOperator" class="fr-header__operator">
<!-- @slot Slot nommé operator pour le logo opérateur. Sera dans `<div class="fr-header__operator">` -->
<slot name="operator">
<img
v-if="operatorImgSrc"
class="fr-responsive-img"
:src="operatorImgSrc"
:alt="operatorImgAlt"
:style="operatorImgStyle"
/>
</slot>
</div>
<div
v-if="showSearch || isWithSlotNav || quickLinks?.length"
class="fr-header__navbar"
>
<button
v-if="showSearch"
class="fr-btn fr-btn--search"
aria-controls="header-search"
aria-label="Recherche"
title="Recherche"
:data-fr-opened="searchModalOpened"
@click.prevent.stop="showSearchModal()"
/>
<button
v-if="isWithSlotNav || quickLinks?.length"
id="button-menu"
class="fr-btn--menu fr-btn"
:data-fr-opened="showMenu"
aria-controls="header-navigation"
aria-haspopup="menu"
aria-label="Menu"
title="Menu"
data-testid="open-menu-btn"
@click.prevent.stop="showMenu()"
/>
</div>
</div>
<div v-if="serviceTitle" class="fr-header__service">
<RouterLink
:to="homeTo"
:title="`Accueil - ${serviceTitle}`"
v-bind="$attrs"
>
<p class="fr-header__service-title">
{{ serviceTitle }}
<span
v-if="showBeta"
class="fr-badge fr-badge--sm fr-badge--green-emeraude"
>
BETA
</span>
</p>
</RouterLink>
<p v-if="serviceDescription" class="fr-header__service-tagline">
{{ serviceDescription }}
</p>
</div>
<div v-if="!serviceTitle && showBeta" class="fr-header__service">
<p class="fr-header__service-title">
<span class="fr-badge fr-badge--sm fr-badge--green-emeraude"
>BETA</span
>
</p>
</div>
</div>
<div class="fr-header__tools">
<div v-if="quickLinks?.length" class="fr-header__tools-links">
<slot name="before-quick-links" />
<nav role="navigation">
<DsfrHeaderMenuLinks
v-if="!menuOpened"
:links="quickLinks"
:nav-aria-label="quickLinksAriaLabel"
/>
</nav>
<slot name="after-quick-links" />
</div>
<div v-if="showSearch" class="fr-header__search fr-modal">
<DsfrSearchBar
:label="searchLabel"
:model-value="modelValue"
:placeholder="placeholder"
style="justify-content: flex-end"
@update:model-value="$emit('update:modelValue', $event)"
@search="$emit('search', $event)"
/>
</div>
</div>
</div>
<div
v-if="
showSearch || isWithSlotNav || (quickLinks && quickLinks.length)
"
id="header-navigation"
class="fr-header__menu fr-modal"
:class="{ 'fr-modal--opened': modalOpened }"
aria-label="Menu modal"
role="dialog"
aria-modal="true"
>
<div class="fr-container">
<button
id="close-button"
class="fr-btn fr-btn--close"
aria-controls="header-navigation"
data-testid="close-modal-btn"
@click.prevent.stop="hideModal()"
>
Fermer
</button>
<div class="fr-header__menu-links">
<slot name="before-quick-links" />
<nav role="navigation">
<DsfrHeaderMenuLinks
v-if="menuOpened"
role="navigation"
:links="quickLinks"
:nav-aria-label="quickLinksAriaLabel"
@link-click="onQuickLinkClick"
/>
</nav>
<slot name="after-quick-links" />
</div>
<template v-if="modalOpened">
<slot name="mainnav" :hidemodal="hideModal" />
</template>
<div
v-if="searchModalOpened"
class="flex justify-center items-center"
>
<DsfrSearchBar
:model-value="modelValue"
:placeholder="placeholder"
@update:model-value="$emit('update:modelValue', $event)"
@search="$emit('search', $event)"
/>
</div>
</div>
</div>
<div
v-if="isWithSlotNav && !modalOpened"
class="fr-hidden fr-unhidden-lg"
>
<!-- @slot Slot nommé mainnav pour le menu de navigation principal -->
<slot name="mainnav" :hidemodal="hideModal" />
</div>
<!-- @slot Slot par défaut pour le contenu du fieldset (sera dans `<div class="fr-header__body-row">`) -->
<slot />
</div>
</div>
</header>
</template>
4 changes: 2 additions & 2 deletions frontend/src/components/ResultPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ async function sendFeedback(isCorrect: boolean) {
</label>
</div>
</div>
<div class="big-blank" />
<div class="small-blank" />
</div>
</template>

Expand All @@ -247,7 +247,7 @@ async function sendFeedback(isCorrect: boolean) {
color: var(--blue-france-sun-113-625);
}
.result-frame {
background-color: #e3e3fd;
background-color: var(--blue-france-925-125);
}
.result-image {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ a {
top: 100%;
left: 50%;
transform: translate(-50%, -100%);
background-color: #ffffff;
background-color: var(--grey-1000-100);
box-shadow: 0 -4px 16px rgb(0 0 0 / 25%);
padding: 15px;
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</p>
<div class="fr-col-sm-6 fr-col-lg-8 mx-auto text-center">
<img
src="@/assets/guide-identification/icones/complements.jpg"
src="@/assets/guide-identification/icones/complements.png"
alt=""
class="img-deco"
aria-hidden="true"
Expand Down
Loading

0 comments on commit 66c2bf0

Please sign in to comment.