mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 22:49:31 -07:00
Add prettier config, format codebase
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<!-- Mobile Drawer -->
|
||||
|
||||
<div v-if="isDrawerOpen" class="drawer-overlay" @click="closeDrawer">
|
||||
<div class="drawer-content" :class="{ 'drawer-open': isDrawerOpen }" @click.stop>
|
||||
<div class="drawer-header sidebar-logo">
|
||||
@@ -7,6 +8,7 @@
|
||||
<router-link to="/" @click="closeDrawer">
|
||||
<img :src="Logo" alt="Logo" class="drawer-logo" />
|
||||
</router-link>
|
||||
|
||||
<button class="icon-button" aria-label="Close" @click="closeDrawer">
|
||||
<i class="pi pi-times"></i>
|
||||
</button>
|
||||
@@ -15,26 +17,41 @@
|
||||
|
||||
<div class="drawer-body">
|
||||
<div class="categories-container">
|
||||
<Category v-for="cat in CATEGORIES" :key="cat.name" :category="cat" :location="route"
|
||||
:pending-active-path="pendingActivePath ?? undefined" :handle-click="onNavClick"
|
||||
:handle-transition-navigation="handleMobileTransitionNavigation" :on-item-mouse-enter="() => { }"
|
||||
:on-item-mouse-leave="() => { }" :is-transitioning="isTransitioning" />
|
||||
<Category
|
||||
v-for="cat in CATEGORIES"
|
||||
:key="cat.name"
|
||||
:category="cat"
|
||||
:location="route"
|
||||
:pending-active-path="pendingActivePath ?? undefined"
|
||||
:handle-click="onNavClick"
|
||||
:handle-transition-navigation="handleMobileTransitionNavigation"
|
||||
:on-item-mouse-enter="() => {}"
|
||||
:on-item-mouse-leave="() => {}"
|
||||
:is-transitioning="isTransitioning"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="useful-links">
|
||||
<p class="useful-links-title">Useful Links</p>
|
||||
|
||||
<div class="links-container">
|
||||
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" @click="closeDrawer" class="useful-link">
|
||||
<span>GitHub</span>
|
||||
|
||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||
</a>
|
||||
|
||||
<router-link to="/text-animations/split-text" @click="closeDrawer" class="useful-link">
|
||||
<span>Docs</span>
|
||||
|
||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||
</router-link>
|
||||
|
||||
<a href="https://davidhaz.com/" target="_blank" @click="closeDrawer" class="useful-link">
|
||||
<span>Who made this?</span>
|
||||
|
||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -44,174 +61,192 @@
|
||||
</div>
|
||||
|
||||
<!-- Desktop Sidebar -->
|
||||
<nav ref="sidebarContainerRef" class="sidebar" :class="{ 'sidebar-no-fade': isScrolledToBottom }"
|
||||
@scroll="handleScroll">
|
||||
|
||||
<nav
|
||||
ref="sidebarContainerRef"
|
||||
class="sidebar"
|
||||
:class="{ 'sidebar-no-fade': isScrolledToBottom }"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div ref="sidebarRef" class="sidebar-content">
|
||||
<!-- Active line indicator -->
|
||||
<div class="active-line" :style="{
|
||||
transform: isLineVisible && linePosition !== null
|
||||
? `translateY(${linePosition - 8}px)`
|
||||
: 'translateY(-100px)',
|
||||
opacity: isLineVisible ? 1 : 0
|
||||
}"></div>
|
||||
|
||||
<div
|
||||
class="active-line"
|
||||
:style="{
|
||||
transform:
|
||||
isLineVisible && linePosition !== null ? `translateY(${linePosition - 8}px)` : 'translateY(-100px)',
|
||||
opacity: isLineVisible ? 1 : 0
|
||||
}"
|
||||
></div>
|
||||
|
||||
<!-- Hover line indicator -->
|
||||
<div class="hover-line" :style="{
|
||||
transform: hoverLinePosition !== null
|
||||
? `translateY(${hoverLinePosition - 8}px)`
|
||||
: 'translateY(-100px)',
|
||||
opacity: isHoverLineVisible ? 1 : 0
|
||||
}"></div>
|
||||
|
||||
<div
|
||||
class="hover-line"
|
||||
:style="{
|
||||
transform: hoverLinePosition !== null ? `translateY(${hoverLinePosition - 8}px)` : 'translateY(-100px)',
|
||||
opacity: isHoverLineVisible ? 1 : 0
|
||||
}"
|
||||
></div>
|
||||
|
||||
<div class="categories-list">
|
||||
<Category v-for="cat in CATEGORIES" :key="cat.name" :category="cat" :location="route"
|
||||
:pending-active-path="pendingActivePath ?? undefined" :handle-click="scrollToTop"
|
||||
:handle-transition-navigation="handleTransitionNavigation" :on-item-mouse-enter="onItemEnter"
|
||||
:on-item-mouse-leave="onItemLeave" :is-transitioning="isTransitioning" />
|
||||
<Category
|
||||
v-for="cat in CATEGORIES"
|
||||
:key="cat.name"
|
||||
:category="cat"
|
||||
:location="route"
|
||||
:pending-active-path="pendingActivePath ?? undefined"
|
||||
:handle-click="scrollToTop"
|
||||
:handle-transition-navigation="handleTransitionNavigation"
|
||||
:on-item-mouse-enter="onItemEnter"
|
||||
:on-item-mouse-leave="onItemLeave"
|
||||
:is-transitioning="isTransitioning"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, nextTick, watch, defineComponent, h, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { CATEGORIES, NEW, UPDATED } from '../../constants/Categories'
|
||||
import Logo from '../../assets/logos/vue-bits-logo.svg'
|
||||
import '../../css/sidebar.css'
|
||||
import { ref, onMounted, onUnmounted, nextTick, watch, defineComponent, h, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { CATEGORIES, NEW, UPDATED } from '../../constants/Categories';
|
||||
import Logo from '../../assets/logos/vue-bits-logo.svg';
|
||||
import '../../css/sidebar.css';
|
||||
|
||||
const HOVER_TIMEOUT_DELAY = 150
|
||||
const HOVER_TIMEOUT_DELAY = 150;
|
||||
|
||||
const isDrawerOpen = ref(false)
|
||||
const linePosition = ref<number | null>(null)
|
||||
const isLineVisible = ref(false)
|
||||
const hoverLinePosition = ref<number | null>(null)
|
||||
const isHoverLineVisible = ref(false)
|
||||
const pendingActivePath = ref<string | null>(null)
|
||||
const isScrolledToBottom = ref(false)
|
||||
const isTransitioning = ref(false)
|
||||
const isDrawerOpen = ref(false);
|
||||
const linePosition = ref<number | null>(null);
|
||||
const isLineVisible = ref(false);
|
||||
const hoverLinePosition = ref<number | null>(null);
|
||||
const isHoverLineVisible = ref(false);
|
||||
const pendingActivePath = ref<string | null>(null);
|
||||
const isScrolledToBottom = ref(false);
|
||||
const isTransitioning = ref(false);
|
||||
|
||||
const sidebarRef = ref<HTMLDivElement>()
|
||||
const sidebarContainerRef = ref<HTMLDivElement>()
|
||||
const sidebarRef = ref<HTMLDivElement>();
|
||||
const sidebarContainerRef = ref<HTMLDivElement>();
|
||||
|
||||
let hoverTimeoutRef: number | null = null
|
||||
let hoverDelayTimeoutRef: number | null = null
|
||||
let hoverTimeoutRef: number | null = null;
|
||||
let hoverDelayTimeoutRef: number | null = null;
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const scrollToTop = () => window.scrollTo(0, 0)
|
||||
const slug = (str: string) => str.replace(/\s+/g, "-").toLowerCase()
|
||||
const scrollToTop = () => window.scrollTo(0, 0);
|
||||
const slug = (str: string) => str.replace(/\s+/g, '-').toLowerCase();
|
||||
|
||||
const findActiveElement = () => {
|
||||
const activePath = pendingActivePath.value || route.path
|
||||
const activePath = pendingActivePath.value || route.path;
|
||||
|
||||
for (const category of CATEGORIES) {
|
||||
const activeItem = category.subcategories.find((sub: string) => {
|
||||
const expectedPath = `/${slug(category.name)}/${slug(sub)}`
|
||||
return activePath === expectedPath
|
||||
})
|
||||
const expectedPath = `/${slug(category.name)}/${slug(sub)}`;
|
||||
return activePath === expectedPath;
|
||||
});
|
||||
if (activeItem) {
|
||||
const selector = `.sidebar a[href="${activePath}"]`
|
||||
const element = document.querySelector(selector) as HTMLElement
|
||||
return element
|
||||
const selector = `.sidebar a[href="${activePath}"]`;
|
||||
const element = document.querySelector(selector) as HTMLElement;
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const updateLinePosition = (el: HTMLElement | null) => {
|
||||
if (!el || !sidebarRef.value || !sidebarRef.value.offsetParent) return null
|
||||
const sidebarRect = sidebarRef.value.getBoundingClientRect()
|
||||
const elRect = el.getBoundingClientRect()
|
||||
return elRect.top - sidebarRect.top + elRect.height / 2
|
||||
}
|
||||
if (!el || !sidebarRef.value || !sidebarRef.value.offsetParent) return null;
|
||||
const sidebarRect = sidebarRef.value.getBoundingClientRect();
|
||||
const elRect = el.getBoundingClientRect();
|
||||
return elRect.top - sidebarRect.top + elRect.height / 2;
|
||||
};
|
||||
|
||||
const closeDrawer = () => {
|
||||
isDrawerOpen.value = false
|
||||
}
|
||||
isDrawerOpen.value = false;
|
||||
};
|
||||
|
||||
const onNavClick = () => {
|
||||
closeDrawer()
|
||||
scrollToTop()
|
||||
}
|
||||
closeDrawer();
|
||||
scrollToTop();
|
||||
};
|
||||
|
||||
const handleTransitionNavigation = async (path: string) => {
|
||||
if (isTransitioning.value || route.path === path) return
|
||||
if (isTransitioning.value || route.path === path) return;
|
||||
|
||||
pendingActivePath.value = path
|
||||
pendingActivePath.value = path;
|
||||
|
||||
// TODO: Implement transition when available
|
||||
await router.push(path)
|
||||
scrollToTop()
|
||||
pendingActivePath.value = null
|
||||
}
|
||||
await router.push(path);
|
||||
scrollToTop();
|
||||
pendingActivePath.value = null;
|
||||
};
|
||||
|
||||
const handleMobileTransitionNavigation = async (path: string) => {
|
||||
if (isTransitioning.value || route.path === path) return
|
||||
if (isTransitioning.value || route.path === path) return;
|
||||
|
||||
closeDrawer()
|
||||
pendingActivePath.value = path
|
||||
closeDrawer();
|
||||
pendingActivePath.value = path;
|
||||
|
||||
// TODO: Implement transition when available
|
||||
await router.push(path)
|
||||
scrollToTop()
|
||||
pendingActivePath.value = null
|
||||
}
|
||||
await router.push(path);
|
||||
scrollToTop();
|
||||
pendingActivePath.value = null;
|
||||
};
|
||||
|
||||
const onItemEnter = (path: string, e: Event) => {
|
||||
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef)
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef)
|
||||
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef);
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef);
|
||||
|
||||
const targetElement = e.currentTarget as HTMLElement
|
||||
const pos = updateLinePosition(targetElement)
|
||||
const targetElement = e.currentTarget as HTMLElement;
|
||||
const pos = updateLinePosition(targetElement);
|
||||
|
||||
if (pos !== null) {
|
||||
hoverLinePosition.value = pos
|
||||
hoverLinePosition.value = pos;
|
||||
}
|
||||
|
||||
hoverDelayTimeoutRef = setTimeout(() => {
|
||||
isHoverLineVisible.value = true
|
||||
}, 200)
|
||||
}
|
||||
isHoverLineVisible.value = true;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const onItemLeave = () => {
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef)
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef);
|
||||
|
||||
hoverTimeoutRef = setTimeout(() => {
|
||||
isHoverLineVisible.value = false
|
||||
}, HOVER_TIMEOUT_DELAY)
|
||||
}
|
||||
isHoverLineVisible.value = false;
|
||||
}, HOVER_TIMEOUT_DELAY);
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
const sidebarElement = sidebarContainerRef.value
|
||||
if (!sidebarElement) return
|
||||
const sidebarElement = sidebarContainerRef.value;
|
||||
if (!sidebarElement) return;
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = sidebarElement
|
||||
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10
|
||||
isScrolledToBottom.value = isAtBottom
|
||||
}
|
||||
const { scrollTop, scrollHeight, clientHeight } = sidebarElement;
|
||||
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10;
|
||||
isScrolledToBottom.value = isAtBottom;
|
||||
};
|
||||
|
||||
const updateActiveLine = async () => {
|
||||
await nextTick()
|
||||
await nextTick();
|
||||
|
||||
setTimeout(() => {
|
||||
const activeEl = findActiveElement()
|
||||
const activeEl = findActiveElement();
|
||||
|
||||
if (!activeEl) {
|
||||
isLineVisible.value = false
|
||||
return
|
||||
isLineVisible.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const pos = updateLinePosition(activeEl)
|
||||
const pos = updateLinePosition(activeEl);
|
||||
if (pos !== null) {
|
||||
linePosition.value = pos
|
||||
isLineVisible.value = true
|
||||
linePosition.value = pos;
|
||||
isLineVisible.value = true;
|
||||
} else {
|
||||
isLineVisible.value = false
|
||||
isLineVisible.value = false;
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const Category = defineComponent({
|
||||
name: 'Category',
|
||||
@@ -251,74 +286,78 @@ const Category = defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
interface ItemType {
|
||||
sub: string
|
||||
path: string
|
||||
isActive: boolean
|
||||
isNew: boolean
|
||||
isUpdated: boolean
|
||||
sub: string;
|
||||
path: string;
|
||||
isActive: boolean;
|
||||
isNew: boolean;
|
||||
isUpdated: boolean;
|
||||
}
|
||||
|
||||
const items = computed(() =>
|
||||
props.category.subcategories.map((sub: string): ItemType => {
|
||||
const path = `/${slug(props.category.name)}/${slug(sub)}`
|
||||
const activePath = props.pendingActivePath || props.location.path
|
||||
const path = `/${slug(props.category.name)}/${slug(sub)}`;
|
||||
const activePath = props.pendingActivePath || props.location.path;
|
||||
return {
|
||||
sub,
|
||||
path,
|
||||
isActive: activePath === path,
|
||||
isNew: (NEW as string[]).includes(sub),
|
||||
isUpdated: (UPDATED as string[]).includes(sub),
|
||||
}
|
||||
isUpdated: (UPDATED as string[]).includes(sub)
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return () => h('div', { class: 'category' }, [
|
||||
h('p', { class: 'category-name' }, props.category.name),
|
||||
h('div', { class: 'category-items' },
|
||||
items.value.map(({ sub, path, isActive, isNew, isUpdated }: ItemType) => {
|
||||
return h('router-link', {
|
||||
key: path,
|
||||
to: path,
|
||||
class: [
|
||||
'sidebar-item',
|
||||
{ 'active-sidebar-item': isActive },
|
||||
{ 'transitioning': props.isTransitioning }
|
||||
],
|
||||
onClick: (e: Event) => {
|
||||
e.preventDefault()
|
||||
props.handleTransitionNavigation(path)
|
||||
},
|
||||
onMouseenter: (e: Event) => props.onItemMouseEnter(path, e),
|
||||
onMouseleave: props.onItemMouseLeave
|
||||
}, {
|
||||
default: () => [
|
||||
sub,
|
||||
isNew ? h('span', { class: 'new-tag' }, 'New') : null,
|
||||
isUpdated ? h('span', { class: 'updated-tag' }, 'Updated') : null
|
||||
].filter(Boolean)
|
||||
return () =>
|
||||
h('div', { class: 'category' }, [
|
||||
h('p', { class: 'category-name' }, props.category.name),
|
||||
h(
|
||||
'div',
|
||||
{ class: 'category-items' },
|
||||
items.value.map(({ sub, path, isActive, isNew, isUpdated }: ItemType) => {
|
||||
return h(
|
||||
'router-link',
|
||||
{
|
||||
key: path,
|
||||
to: path,
|
||||
class: ['sidebar-item', { 'active-sidebar-item': isActive }, { transitioning: props.isTransitioning }],
|
||||
onClick: (e: Event) => {
|
||||
e.preventDefault();
|
||||
props.handleTransitionNavigation(path);
|
||||
},
|
||||
onMouseenter: (e: Event) => props.onItemMouseEnter(path, e),
|
||||
onMouseleave: props.onItemMouseLeave
|
||||
},
|
||||
{
|
||||
default: () =>
|
||||
[
|
||||
sub,
|
||||
isNew ? h('span', { class: 'new-tag' }, 'New') : null,
|
||||
isUpdated ? h('span', { class: 'updated-tag' }, 'Updated') : null
|
||||
].filter(Boolean)
|
||||
}
|
||||
);
|
||||
})
|
||||
})
|
||||
)
|
||||
])
|
||||
)
|
||||
]);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
watch(() => route.path, updateActiveLine)
|
||||
watch(pendingActivePath, updateActiveLine)
|
||||
watch(() => route.path, updateActiveLine);
|
||||
watch(pendingActivePath, updateActiveLine);
|
||||
|
||||
onMounted(() => {
|
||||
updateActiveLine()
|
||||
updateActiveLine();
|
||||
if (sidebarContainerRef.value) {
|
||||
sidebarContainerRef.value.addEventListener('scroll', handleScroll)
|
||||
handleScroll()
|
||||
sidebarContainerRef.value.addEventListener('scroll', handleScroll);
|
||||
handleScroll();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef)
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef)
|
||||
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef);
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef);
|
||||
if (sidebarContainerRef.value) {
|
||||
sidebarContainerRef.value.removeEventListener('scroll', handleScroll)
|
||||
sidebarContainerRef.value.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user