FEAT: Added in timeline element
This commit is contained in:
parent
c52d185f76
commit
fd3c620cb9
@ -1,38 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
function onClick() {
|
|
||||||
dispatch('click');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<div
|
|
||||||
class="sliding-card flex flex-col justify-between flex-wrap flex-[2_1_15em] p-2 bg-secondary rounded-lg snap-start transition-all duration-300 overflow-hidden relative hover:shadow-lg"
|
|
||||||
on:click={onClick}
|
|
||||||
>
|
|
||||||
<div class="sliding-card-header w-full flex items-center justify-between mb-0">
|
|
||||||
<slot name="header"></slot>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<!-- Wrapper to stack sliding-card-content and sliding-content -->
|
|
||||||
<div class="content-wrapper relative w-full overflow-hidden">
|
|
||||||
<div class="sliding-card-content flex flex-col items-center justify-center max-w-full flex-grow z-[1]">
|
|
||||||
<slot name="content"></slot>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="sliding-content absolute top-0 left-0 w-full h-full bg-secondary translate-y-full transition-transform duration-300 z-[2] pointer-events-none group-hover:translate-y-0 sliding-card:hover:translate-y-0"
|
|
||||||
>
|
|
||||||
<slot name="sliding-content"></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr class="not-required" />
|
|
||||||
<div class="sliding-card-footer flex gap-4 max-w-full justify-between mb-4">
|
|
||||||
<slot name="footer"></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
41
src/lib/components/Timeline.svelte
Normal file
41
src/lib/components/Timeline.svelte
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let timelineData: Array<{
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
duration: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
// Track open/closed state for each entry
|
||||||
|
let openStates = timelineData.map(() => false);
|
||||||
|
|
||||||
|
function toggle(index: number) {
|
||||||
|
openStates[index] = !openStates[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(0); // Open the first entry by default
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<div class="max-w-4xl w-full">
|
||||||
|
{#each timelineData as entry, i}
|
||||||
|
<div class="relative border-l border-gray-700 pl-8 pb-12">
|
||||||
|
{#if openStates[i]}
|
||||||
|
<div class="absolute top-0 left-[8px] text-green-400 w-4 h-4">♦</div>
|
||||||
|
{:else}
|
||||||
|
<div class="absolute top-0 left-[8px] text-green-400 w-4 h-4">⋄</div>
|
||||||
|
{/if}
|
||||||
|
<p class="text-sm opacity-70">{entry.duration}</p>
|
||||||
|
<button
|
||||||
|
class="text-2lg font-semibold text-red-400 mt-1 focus:outline-none hover:underline transition"
|
||||||
|
on:click={() => toggle(i)}
|
||||||
|
aria-expanded={openStates[i]}
|
||||||
|
>
|
||||||
|
<h3 class="text-2lg font-semibold text-red-400 mt-1">{entry.title}</h3>
|
||||||
|
</button>
|
||||||
|
{#if openStates[i]}
|
||||||
|
<p class="mt-2 whitespace-pre-line transition-all duration-300">{@html entry.description}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,21 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { setContext } from 'svelte';
|
|
||||||
import type { TimelinePosition, TimelineConfig } from '$lib/types';
|
|
||||||
export let position: TimelinePosition = 'right';
|
|
||||||
export let style: string | null = null;
|
|
||||||
|
|
||||||
setContext<TimelineConfig>('TimelineConfig', { rootPosition: position });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ul class="timeline" {style}>
|
|
||||||
<slot />
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.timeline {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 6px 16px;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,13 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let style: string = "";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<span class="timeline-connector" {style}></span>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.timeline-connector {
|
|
||||||
width: 2px;
|
|
||||||
background-color: #bdbdbd;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,30 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { getContext } from 'svelte';
|
|
||||||
import type { TimelineConfig, TimelinePosition } from '$lib/types';
|
|
||||||
export let style: string | null = null;
|
|
||||||
|
|
||||||
const config = getContext<TimelineConfig>('TimelineConfig');
|
|
||||||
const parentPosition = getContext<TimelinePosition>('ParentPosition');
|
|
||||||
|
|
||||||
const itemPosition = parentPosition ? parentPosition : config.rootPosition;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class={`timeline-content ${itemPosition}`} {style}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.timeline-content {
|
|
||||||
margin: 0;
|
|
||||||
flex: 1;
|
|
||||||
margin: 6px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,19 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let style: string | null = null;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<span class="timeline-dot" {style}>
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.timeline-dot {
|
|
||||||
background-color: #121212;
|
|
||||||
border: solid 2px #121212;
|
|
||||||
display: flex;
|
|
||||||
align-self: baseline;
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin: 11.5px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,58 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { getContext, setContext } from 'svelte';
|
|
||||||
import type { TimelinePosition, ParentPosition, TimelineConfig } from '$lib/types';
|
|
||||||
|
|
||||||
export let position: ParentPosition | null = null;
|
|
||||||
export let style: string = "";
|
|
||||||
|
|
||||||
const config = getContext<TimelineConfig>('TimelineConfig');
|
|
||||||
const itemPosition = position ? position : config.rootPosition;
|
|
||||||
setContext<TimelinePosition>('ParentPosition', itemPosition);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<li class={`timeline-item ${itemPosition}`} {style}>
|
|
||||||
{#if !$$slots['opposite-content']}
|
|
||||||
<div class="opposite-block"></div>
|
|
||||||
{:else}
|
|
||||||
<slot name="opposite-content" />
|
|
||||||
{/if}
|
|
||||||
<slot />
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
:global(.alternate:nth-of-type(even) > .timeline-content) {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.alternate:nth-of-type(odd) > .timeline-opposite-content) {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.opposite-block {
|
|
||||||
flex: 1;
|
|
||||||
margin: 6px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-item {
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
min-height: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alternate:nth-of-type(even) {
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alternate:nth-of-type(odd) {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,31 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { getContext } from 'svelte';
|
|
||||||
import type { TimelineConfig, TimelinePosition } from '$lib/types';
|
|
||||||
export let style: string | null = null;
|
|
||||||
|
|
||||||
const config = getContext<TimelineConfig>('TimelineConfig');
|
|
||||||
const parentPosition = getContext<TimelinePosition>('ParentPosition');
|
|
||||||
|
|
||||||
const itemPosition = parentPosition ? parentPosition : config.rootPosition;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class={`timeline-opposite-content ${itemPosition}`} {style}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.timeline-opposite-content {
|
|
||||||
margin: 0;
|
|
||||||
flex: 1;
|
|
||||||
margin-right: auto;
|
|
||||||
margin: 6px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,16 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let style: string | null = null;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="timeline-separator" {style}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.timeline-separator {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 0;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,41 +1,9 @@
|
|||||||
// place files you want to import through the `$lib` alias in this folder.
|
// place files you want to import through the `$lib` alias in this folder.
|
||||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
|
||||||
import TimelineItem from '$lib/components/timeline/TimelineItem.svelte';
|
|
||||||
import TimelineSeparator from '$lib/components/timeline/TimelineSeparator.svelte';
|
|
||||||
import TimelineDot from '$lib/components/timeline/TimelineDot.svelte';
|
|
||||||
import TimelineConnector from '$lib/components/timeline/TimelineConnector.svelte';
|
|
||||||
import TimelineContent from '$lib/components/timeline/TimelineContent.svelte';
|
|
||||||
import TimelineOppositeContent from '$lib/components/timeline/TimelineOppositeContent.svelte';
|
|
||||||
|
|
||||||
import Toasts from '$lib/components/Toasts/Toasts.svelte';
|
|
||||||
import Toast from '$lib/components/Toasts/Toast.svelte';
|
|
||||||
import CloseIcon from '$lib/components/Toasts/CloseIcon.svelte';
|
|
||||||
import InfoIcon from '$lib/components/Toasts/InfoIcon.svelte';
|
|
||||||
import SuccessIcon from '$lib/components/Toasts/SuccessIcon.svelte';
|
|
||||||
import ErrorIcon from '$lib/components/Toasts/ErrorIcon.svelte';
|
|
||||||
|
|
||||||
import Card from '$lib/components/Cards/Card.svelte';
|
import Card from '$lib/components/Cards/Card.svelte';
|
||||||
import SlidingCard from '$lib/components/Cards/SlidingCard.svelte';
|
import FlexGallery from './components/FlexGallery.svelte';
|
||||||
import Modal from '$lib/components/Modal.svelte';
|
import Loading from './components/Loading.svelte';
|
||||||
|
import Section from './components/Section.svelte';
|
||||||
|
import SkillProgress from './components/SkillProgress.svelte';
|
||||||
|
import Timeline from './components/Timeline.svelte';
|
||||||
|
|
||||||
|
export { Card, FlexGallery, Loading, Section, SkillProgress, Timeline };
|
||||||
export {
|
|
||||||
Timeline,
|
|
||||||
TimelineItem,
|
|
||||||
TimelineSeparator,
|
|
||||||
TimelineDot,
|
|
||||||
TimelineConnector,
|
|
||||||
TimelineContent,
|
|
||||||
TimelineOppositeContent,
|
|
||||||
|
|
||||||
Toasts,
|
|
||||||
Toast,
|
|
||||||
CloseIcon,
|
|
||||||
InfoIcon,
|
|
||||||
SuccessIcon,
|
|
||||||
ErrorIcon,
|
|
||||||
|
|
||||||
Card,
|
|
||||||
SlidingCard,
|
|
||||||
Modal
|
|
||||||
};
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
import Card from '$lib/components/Cards/Card.svelte';
|
import Card from '$lib/components/Cards/Card.svelte';
|
||||||
import FlexGallery from "$lib/components/FlexGallery.svelte";
|
import FlexGallery from "$lib/components/FlexGallery.svelte";
|
||||||
import SkillProgress from "$lib/components/SkillProgress.svelte";
|
import SkillProgress from "$lib/components/SkillProgress.svelte";
|
||||||
import Timeline from './timeline.svelte';
|
import Timeline from '$lib/components/Timeline.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await getJson('/json/me.json')}
|
{#await getJson('/json/me.json')}
|
||||||
@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Card -->
|
<!-- Main Card -->
|
||||||
<Section label="Experience">
|
<Section label="[Experience]">
|
||||||
<Card headerLeft={info.name} headerRight={info.job_title} footer={info.location}>
|
<Card headerLeft={info.name} headerRight={info.job_title} footer={info.location}>
|
||||||
<div class="flex flex-row items-center gap-5">
|
<div class="flex flex-row items-center gap-5">
|
||||||
<img
|
<img
|
||||||
@ -38,7 +38,7 @@
|
|||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<!-- SKills -->
|
<!-- SKills -->
|
||||||
<Section label="Skills">
|
<Section label="[Skills]">
|
||||||
<FlexGallery>
|
<FlexGallery>
|
||||||
{#each info.skills as skill}
|
{#each info.skills as skill}
|
||||||
<Card headerLeft={skill.name} footer={skill.link} containerStyle="flex-1 min-w-[250px] max-w-full md:min-w-[33%] opacity-100 hover:opacity-100 hover:scale-[105%] md:opacity-70 transition-all duration-300">
|
<Card headerLeft={skill.name} footer={skill.link} containerStyle="flex-1 min-w-[250px] max-w-full md:min-w-[33%] opacity-100 hover:opacity-100 hover:scale-[105%] md:opacity-70 transition-all duration-300">
|
||||||
@ -50,8 +50,8 @@
|
|||||||
</FlexGallery>
|
</FlexGallery>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section label="Experience">
|
<Section label="[Experience]">
|
||||||
<Timeline timelineData={info.experience} />
|
<Timeline timelineData={info.timeline} />
|
||||||
</Section>
|
</Section>
|
||||||
{:catch}
|
{:catch}
|
||||||
<div style="display: none;">
|
<div style="display: none;">
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let timelineData: any;
|
|
||||||
|
|
||||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
|
||||||
import TimelineItem from '$lib/components/timeline/TimelineItem.svelte';
|
|
||||||
import TimelineSeparator from '$lib/components/timeline/TimelineSeparator.svelte';
|
|
||||||
import TimelineDot from '$lib/components/timeline/TimelineDot.svelte';
|
|
||||||
import TimelineConnector from '$lib/components/timeline/TimelineConnector.svelte';
|
|
||||||
import TimelineContent from '$lib/components/timeline/TimelineContent.svelte';
|
|
||||||
import TimelineOppositeContent from '$lib/components/timeline/TimelineOppositeContent.svelte';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Timeline
|
|
||||||
position="alternate"
|
|
||||||
style={`
|
|
||||||
border-radius: 3%;
|
|
||||||
padding: 1rem;
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{#each timelineData as item}
|
|
||||||
<TimelineItem>
|
|
||||||
<TimelineOppositeContent slot="opposite-content">
|
|
||||||
<p class="oposite-content-title">{item.duration}</p>
|
|
||||||
</TimelineOppositeContent>
|
|
||||||
|
|
||||||
<TimelineSeparator>
|
|
||||||
{#if item.duration.includes('Present') || !item.duration.includes('-')}
|
|
||||||
<div class="elementToFadeInAndOut">
|
|
||||||
<TimelineDot
|
|
||||||
style={`background-color: var(--link); border-color: var(--accent);`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<TimelineDot
|
|
||||||
style={`background-color: var(--link); border-color: var(--accent);`}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<TimelineConnector />
|
|
||||||
</TimelineSeparator>
|
|
||||||
<TimelineContent>
|
|
||||||
<h3 class="content-title">{item.title}</h3>
|
|
||||||
<p class="content-description">{@html item.description}</p>
|
|
||||||
</TimelineContent>
|
|
||||||
</TimelineItem>
|
|
||||||
{/each}
|
|
||||||
</Timeline>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.oposite-content-title {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
.content-title {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-description {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin-top: 1rem;
|
|
||||||
color: var(--fg);
|
|
||||||
font-weight: lighter;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
Loading…
x
Reference in New Issue
Block a user