Frontend: Created sidebar.svelte for better management, created topbar.js to close and open topbar. Sidebar is now working on mobile.

This commit is contained in:
Yohan Boujon 2024-02-01 23:57:37 +01:00
parent 213e179e70
commit 4a344b8aff
8 changed files with 371 additions and 119 deletions

View file

@ -75,7 +75,7 @@
}); });
function hidePopup(event) { function hidePopup(event) {
if (!active && popupMain.style.visibility === "visible") { if (!active) {
return; return;
} }

View file

@ -0,0 +1,129 @@
<script>
import { onMount } from "svelte";
import SvgIcon from "@jamescoyle/svelte-icon";
import {
mdiAccount,
mdiCogs,
mdiEmailOutline,
mdiPhone,
mdiStar,
mdiClose,
} from "@mdi/js";
import SidebarComponent from "$lib/components/sidebar-component.svelte";
import { formatDate } from "$lib/js/date.js";
import { showSidebar } from "$lib/js/topbar.js";
import "$lib/css/sidebar.css";
export let info;
export let footer = null;
export let containerCv = null;
export let sidebarContainer;
let sidebar;
const birth_year = formatDate(info.birth_year);
$: scrollY = 0;
$: innerHeight = 0;
function sidebarScrollingHandler() {
// Disabled some functionnalities if no footer nor containerCv is provided
if (footer === null || containerCv === null) {
return;
}
let isBottom = scrollY + innerHeight >= footer.offsetTop;
let isMoved = scrollY + innerHeight >= sidebar.offsetHeight;
let littleScreen = innerHeight < sidebar.offsetHeight;
// Only having the sticky sidebar if the size of the screen is too 'little'
// Testing if sidebar is outside of scrolling scope
if (isMoved && !isBottom) {
sidebar.style.position = "fixed";
sidebar.style.top = littleScreen
? `${innerHeight - sidebar.offsetHeight}px`
: "0px";
}
// Checking if at the bottom, calculating the diff. between the cv and the sidebar heights
else if (isBottom && littleScreen) {
sidebar.style.position = "absolute";
sidebar.style.top = `${
containerCv.offsetHeight - sidebar.offsetHeight
}px`;
}
// Only putting absolute if on little screen
else if (littleScreen) {
sidebar.style.position = "absolute";
sidebar.style.top = "";
}
}
onMount(async () => {
sidebarScrollingHandler();
});
</script>
<svelte:window
bind:scrollY
bind:innerHeight
on:scroll={() => {
sidebarScrollingHandler();
}}
/>
<!-- if no footer nor containerCv is provided we are in mobile mode -->
{#if footer === null || containerCv === null}
<div id="sidebar-container" bind:this={sidebarContainer}>
<button on:click={() => showSidebar()}>
<SvgIcon size="23" path={mdiClose} type="mdi" />
</button>
<div class="sidebar">
<div class="sidebar-profilepic-container">
<img
class="sidebar-profilepic"
src={info.profile_pic}
alt={info.full_name}
/>
</div>
<SidebarComponent icon={mdiAccount} description={birth_year} />
<SidebarComponent icon={mdiEmailOutline} description={info.email} />
{#if info.phone_number != null}
<SidebarComponent icon={mdiPhone} description={info.phone_number} />
{/if}
<SidebarComponent
icon={mdiStar}
title="Interests"
description={info.interests}
/>
<SidebarComponent
icon={mdiCogs}
title="Soft-Skills"
description={info.softskills}
/>
</div>
</div>
{:else}
<div class="sidebar" bind:this={sidebar}>
<div class="sidebar-profilepic-container">
<img
class="sidebar-profilepic"
src={info.profile_pic}
alt={info.full_name}
/>
</div>
<SidebarComponent icon={mdiAccount} description={birth_year} />
<SidebarComponent icon={mdiEmailOutline} description={info.email} />
{#if info.phone_number != null}
<SidebarComponent icon={mdiPhone} description={info.phone_number} />
{/if}
<SidebarComponent
icon={mdiStar}
title="Interests"
description={info.interests}
/>
<SidebarComponent
icon={mdiCogs}
title="Soft-Skills"
description={info.softskills}
/>
</div>
<div class="fake-sidebar" />
{/if}

View file

@ -1,32 +1,21 @@
.container-cv { @media screen and (min-width: 1200px) {
.container-cv {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
}
@media screen and (min-width: 1200px)
{
.sidebar {
position: absolute;
width: 15rem;
transition: all 0s ease 0s;
margin-left: 1rem;
} }
.fake-sidebar { #topbar {
width: 20rem;
box-shadow: rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px;
background-color: var(--color-special);
}
}
@media screen and (max-width: 1200px)
{
.sidebar {
display: none; display: none;
} }
} }
@media screen and (max-width: 1200px) {
.container-cv {
display: flex;
flex-direction: column;
}
}
@media screen and (min-width: 1000px) { @media screen and (min-width: 1000px) {
.name { .name {
text-align: center; text-align: center;
@ -64,11 +53,80 @@
} }
@media screen and (max-width: 1000px) { @media screen and (max-width: 1000px) {
#topbar {
width: 100%;
position: fixed;
z-index: 3;
backdrop-filter: blur(1rem);
height: 6.6rem;
display: flex;
align-items: center;
transition: all .3s ease 0s;
border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem;
}
#topbar>button {
transition: all .3s ease 0s;
cursor: pointer;
border: 0;
color: var(--color-background);
background-color: var(--color-special);
border-radius: 50%;
min-width: 2.5rem;
min-height: 2.5rem;
display: none;
justify-content: center;
align-items: center;
margin-left: 1rem;
box-shadow: 0px 8px 12px -1px #0174BE50;
}
#topbar>button:hover {
color: var(--color-special);
background-color: var(--color-background);
}
#topbar>button:active {
transform: translateY(3px);
box-shadow: 0px 3px 8px -1px #0174BEAA;
}
#fake-topbar {
width: 100%;
height: 6.6rem;
background-color: var(--color-special);
}
.topbar-name {
width: 100%;
color: var(--color-background);
text-align: center;
text-shadow: -15px 5px 20px #00000030;
padding: 0;
margin: 0.5rem;
font-size: 3rem;
transition: all .3s ease 0s;
}
.topbar-name-little {
width: 100%;
color: var(--color-special);
text-align: end;
text-shadow: -15px 5px 20px #00000030;
padding: 0;
margin: 0.5rem;
margin-right: 1rem;
font-size: 1.5rem;
font-weight: 100;
transition: all .3s ease 0s;
}
.name { .name {
text-align: center; text-align: center;
text-shadow: -15px 5px 20px #00000030; text-shadow: -15px 5px 20px #00000030;
background-color: var(--color-special);
color: var(--color-background); color: var(--color-background);
background-color: var(--color-special);
margin-top: 0; margin-top: 0;
padding-top: 1.5rem; padding-top: 1.5rem;
margin-bottom: 0; margin-bottom: 0;
@ -106,19 +164,6 @@
} }
} }
.profile-picture-container {
display: flex;
justify-content: center;
align-items: center;
}
.profile-picture {
width: 9rem;
height: auto;
margin: 2rem;
box-shadow: rgba(0, 0, 0, 0.15) 0px 15px 25px, rgba(0, 0, 0, 0.05) 0px 5px 10px;
}
.main { .main {
width: 100%; width: 100%;
overflow-x: hidden; overflow-x: hidden;

View file

@ -0,0 +1,79 @@
@media screen and (min-width: 1200px) {
.sidebar {
position: absolute;
width: 15rem;
transition: all 0s ease 0s;
margin-left: 1rem;
}
.fake-sidebar {
width: 20rem;
box-shadow: rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px;
background-color: var(--color-special);
}
#sidebar-container {
display: none;
}
}
@media screen and (max-width: 1200px) {
#sidebar-container {
width: 100%;
height: 100%;
position: fixed;
z-index: 5;
visibility: hidden;
background-color: #0176bed0;
backdrop-filter: blur(1rem);
display: flex;
flex-direction: column;
}
#sidebar-container>button {
cursor: pointer;
border: 0;
color: var(--color-background);
background-color: #ffffff00;
border-radius: 50%;
max-width: 2.5rem;
min-height: 2.5rem;
justify-content: center;
align-items: center;
margin-left: 1rem;
/* to align with the open button*/
margin-top: 6.5px;
display: flex;
}
#sidebar-container>button:hover {
color: var(--color-special);
background-color: var(--color-background);
box-shadow: 0px 8px 12px -1px #0174BE50;
}
#sidebar-container>button:active {
transform: translateY(3px);
box-shadow: 0px 3px 8px -1px #0174BEAA;
}
.sidebar {
height: fit-content;
background-color: #ffffff00;
overflow-y: auto;
padding-top: 5rem;
}
}
.sidebar-profilepic-container {
display: flex;
justify-content: center;
align-items: center;
}
.sidebar-profilepic {
width: 9rem;
height: auto;
margin: 2rem;
box-shadow: rgba(0, 0, 0, 0.15) 0px 15px 25px, rgba(0, 0, 0, 0.05) 0px 5px 10px;
}

View file

@ -8,7 +8,7 @@
@media screen and (min-width: 1000px) @media screen and (min-width: 1000px)
{ {
.slideshow_btn { .slideshow_btn {
z-index: 3; z-index: 2;
position: absolute; position: absolute;
right: 0; right: 0;
margin-right: 2.5rem; margin-right: 2.5rem;
@ -30,7 +30,7 @@
@media screen and (max-width: 1000px) @media screen and (max-width: 1000px)
{ {
.slideshow_btn { .slideshow_btn {
z-index: 3; z-index: 2;
position: absolute; position: absolute;
right: 0; right: 0;
margin-right: 1rem; margin-right: 1rem;

View file

@ -5,7 +5,7 @@ export function showPopup(show, projectId) {
const mainPopup = document.getElementById('project-popup-main'); const mainPopup = document.getElementById('project-popup-main');
const body = document.getElementsByTagName('body'); const body = document.getElementsByTagName('body');
if (show === true) { if (show) {
body[0].style.overflow = 'hidden'; body[0].style.overflow = 'hidden';
background.style.visibility = 'visible'; background.style.visibility = 'visible';
mainPopup.style.visibility = 'visible'; mainPopup.style.visibility = 'visible';

View file

@ -0,0 +1,14 @@
export function showSidebar(show) {
const sidebarContainer = document.getElementById('sidebar-container');
const body = document.getElementsByTagName('body');
console.log(body[0].style.overflow);
if (show) {
body[0].style.overflow = 'hidden';
sidebarContainer.style.visibility = 'visible';
} else {
body[0].style.overflow = '';
sidebarContainer.style.visibility = 'hidden';
}
console.log(body[0].style.overflow);
}

View file

@ -1,25 +1,12 @@
<script> <script>
import SvgIcon from "@jamescoyle/svelte-icon"; import SvgIcon from "@jamescoyle/svelte-icon";
import { processData } from "$lib/js/processdata.js"; import { processData } from "$lib/js/processdata.js";
import { formatDate } from "$lib/js/date.js"; import { showSidebar } from "$lib/js/topbar.js";
import "$lib/css/base.css"; import "$lib/css/base.css";
import "$lib/css/cv.css"; import "$lib/css/cv.css";
// Sidebar
import SidebarComponent from "$lib/components/sidebar-component.svelte";
import {
mdiAccount,
mdiCogs,
mdiEmailOutline,
mdiPhone,
mdiStar,
mdiXml,
mdiApplication,
mdiEarth,
mdiHeart,
} from "@mdi/js";
// Main // Main
import Sidebar from "$lib/components/sidebar.svelte";
import Section from "$lib/components/section.svelte"; import Section from "$lib/components/section.svelte";
import SubSection from "$lib/components/subsection.svelte"; import SubSection from "$lib/components/subsection.svelte";
import Education from "$lib/components/education.svelte"; import Education from "$lib/components/education.svelte";
@ -29,94 +16,92 @@
import Pill from "$lib/components/pill.svelte"; import Pill from "$lib/components/pill.svelte";
import FlagComponent from "$lib/components/flag-component.svelte"; import FlagComponent from "$lib/components/flag-component.svelte";
import ProjectsPopup from "$lib/components/projects-popup.svelte"; import ProjectsPopup from "$lib/components/projects-popup.svelte";
import { mdiSchool, mdiBriefcase, mdiWrench, mdiPencil } from "@mdi/js"; import {
mdiSchool,
mdiBriefcase,
mdiWrench,
mdiPencil,
mdiAccount,
mdiXml,
mdiApplication,
mdiEarth,
mdiHeart,
} from "@mdi/js";
import { onMount } from "svelte"; import { onMount } from "svelte";
export let data; export let data;
const cv = data.status == 0 ? processData(data) : undefined; const cv = data.status == 0 ? processData(data) : undefined;
const birth_year =
data.status == 0 ? formatDate(cv.info.birth_year) : undefined;
// Sidebar sticky // Sidebar
let sidebar;
let containerCv; let containerCv;
let footer; let footer;
$: scrollY = 0;
$: innerHeight = 0;
onMount(() => {
sidebarScrollingHandler();
});
function sidebarScrollingHandler() { // Mobile top bar
let isBottom = scrollY + innerHeight >= footer.offsetTop; function mobileTopBar() {
let isMoved = scrollY + innerHeight >= sidebar.offsetHeight; // 53 px or half the topbar size
let littleScreen = innerHeight < sidebar.offsetHeight; if (scrollY > 53) {
// Only having the sticky sidebar if the size of the screen is too 'little' topbar.style.height = "53px";
topbar.style.backgroundColor = "#F8F1F1AE";
// Testing if sidebar is outside of scrolling scope topbar.style.boxShadow = "0px 8px 18px -1px #1d4a6560";
if (isMoved && !isBottom) { buttonTopbar.style.display = "flex";
sidebar.style.position = "fixed"; } else {
sidebar.style.top = littleScreen topbar.style.height = "";
? `${innerHeight - sidebar.offsetHeight}px` topbar.style.backgroundColor = "#F8F1F100";
: "0px"; topbar.style.boxShadow = "";
} buttonTopbar.style.display = "none";
// Checking if at the bottom, calculating the diff. between the cv and the sidebar heights
else if (isBottom && littleScreen) {
sidebar.style.position = "absolute";
sidebar.style.top = `${
containerCv.offsetHeight - sidebar.offsetHeight
}px`;
}
// Only putting absolute if on little screen
else if (littleScreen) {
sidebar.style.position = "absolute";
sidebar.style.top = "";
} }
} }
// Mobile check // Mobile check
$: innerWidth = 0; $: innerWidth = 0;
$: scrollY = 0;
let topbar;
let sidebarContainer;
let buttonTopbar;
onMount(async () => {
mobileTopBar();
});
</script> </script>
<svelte:window <svelte:window
bind:scrollY bind:scrollY
bind:innerHeight
bind:innerWidth bind:innerWidth
on:scroll={sidebarScrollingHandler} on:scroll={() => {
if (innerWidth < 1200) {
mobileTopBar();
}
}}
/> />
{#if data.status == 0} {#if data.status == 0}
<ProjectsPopup tags={cv.tags} /> <ProjectsPopup tags={cv.tags} />
<div class="container-cv" bind:this={containerCv}> <!-- TOPBAR DIV (POPUP: mobile) -->
<!-- SIDEBAR DIV (LEFT) --> {#if innerWidth < 1200}
<div class="sidebar" bind:this={sidebar}> <Sidebar info={cv.info} bind:sidebarContainer />
<div class="profile-picture-container">
<img
class="profile-picture"
src={cv.info.profile_pic}
alt={cv.info.full_name}
/>
</div>
<SidebarComponent icon={mdiAccount} description={birth_year} />
<SidebarComponent icon={mdiEmailOutline} description={cv.info.email} />
{#if cv.info.phone_number != null}
<SidebarComponent icon={mdiPhone} description={cv.info.phone_number} />
{/if} {/if}
<SidebarComponent <div class="container-cv" bind:this={containerCv}>
icon={mdiStar} <!-- SIDEBAR DIV (LEFT: desktop) -->
title="Interests" {#if innerWidth >= 1200}
description={cv.info.interests} <Sidebar info={cv.info} {footer} {containerCv} />
/> {/if}
<SidebarComponent <!-- MOBILE TOP BAR -->
icon={mdiCogs} {#if innerWidth < 1000}
title="Soft-Skills" <div id="topbar" bind:this={topbar}>
description={cv.info.softskills} <button on:click={() => showSidebar(true)} bind:this={buttonTopbar}>
/> <SvgIcon size="23" path={mdiAccount} type="mdi" />
</button>
<h1 class={scrollY <= 53 ? "topbar-name" : "topbar-name-little"}>
{cv.info.full_name}
</h1>
</div> </div>
<div class="fake-sidebar" /> <div id="fake-topbar" />
<!-- MAIN DIV (RIGHT) --> {/if}
<!-- MAIN DIV (RIGHT: desktop/CENTER: mobile) -->
<div class="main"> <div class="main">
{#if innerWidth >= 1000}
<h1 class="name">{cv.info.full_name}</h1> <h1 class="name">{cv.info.full_name}</h1>
{/if}
<h2 class="name">Apprentice Engineer Automatic/Electronic</h2> <h2 class="name">Apprentice Engineer Automatic/Electronic</h2>
<Section icon={mdiSchool} title="Education" /> <Section icon={mdiSchool} title="Education" />
<SlideShow <SlideShow