You've already forked AstralRinth
forked from didirus/AstralRinth
Project, Search, User redesign (#1281)
* New project page * fix silly icon tailwind classes * Start new versions page, add new ButtonStyled component * Pagination and finish mocking up versions page functionality * green download button * hover animation * New Modal, Avatar refactor, subpages in NavTabs * lint * Download modal * New user page + fix lint * fix ui lint * Download animation fix * Versions filter + finish project page * Improve consistency of buttons on home page * Fix ButtonStyled breaking * Fix margin on version summary * finish search, new modals, user + project page mobile * fix gallery image pages * New project header * Fix gallery tab showing improperly * Use auto direction + position for all popouts * Preliminary user page * test to see if this fixes login stuff * remove extra slash * Add version actions, move download button on versions page * Listed -> public * Shorten download modal selector height * Fix user menu open direction * Change breakpoint for header collapse * Only underline title * Tighten padding on stats a little * New nav * Make mobile breakpoint more consistent * fix header breakpoint regression * Add sign in button * Fix edit icon color * Fix margin at top of screen * Fix user bios and ad width * Fix user nav showing when there's only one type of project * Fix plural projects on user page & extract i18n * Remove ads on mobile for now * Fix overflow menu showing hidden items * NavTabs on mobile * Fix navbar z index * Search filter overhaul + negative filters * fix no-max-height * port version filters, fix following/collections, lint * hide promos * ui lint * Disable modal background animation to reduce reported motion sickness * Hide install with modrinth app button on mobile --------- Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com> Co-authored-by: Prospector <prospectordev@gmail.com>
This commit is contained in:
@@ -1,63 +1,84 @@
|
||||
<template>
|
||||
<nav class="relative flex w-fit overflow-clip rounded-full bg-bg-raised p-1 text-sm font-bold">
|
||||
<nav
|
||||
class="experimental-styles-within relative flex w-fit overflow-clip rounded-full bg-bg-raised p-1 text-sm font-bold"
|
||||
>
|
||||
<NuxtLink
|
||||
v-for="(link, index) in filteredLinks"
|
||||
v-show="link.shown === undefined ? true : link.shown"
|
||||
:key="index"
|
||||
ref="linkElements"
|
||||
ref="tabLinkElements"
|
||||
:to="query ? (link.href ? `?${query}=${link.href}` : '?') : link.href"
|
||||
class="button-animation flex flex-row items-center gap-2 px-4 py-2 focus:rounded-full"
|
||||
:class="{ 'text-brand': activeIndex === index }"
|
||||
class="button-animation z-[1] flex flex-row items-center gap-2 px-4 py-2 focus:rounded-full"
|
||||
:class="{
|
||||
'text-brand': activeIndex === index && !subpageSelected,
|
||||
'text-contrast': activeIndex === index && subpageSelected,
|
||||
}"
|
||||
>
|
||||
<component :is="link.icon" v-if="link.icon" class="size-5" />
|
||||
<span>{{ link.label }}</span>
|
||||
<span class="text-nowrap">{{ link.label }}</span>
|
||||
</NuxtLink>
|
||||
<div
|
||||
class="pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full bg-brand p-1 transition-all"
|
||||
:class="`navtabs-transition pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full p-1 ${subpageSelected ? 'bg-button-bg' : 'bg-brand-highlight'}`"
|
||||
:style="{
|
||||
left: positionToMoveX,
|
||||
top: positionToMoveY,
|
||||
width: sliderWidth,
|
||||
opacity: activeIndex === -1 ? 0 : 0.25,
|
||||
left: sliderLeftPx,
|
||||
top: sliderTopPx,
|
||||
right: sliderRightPx,
|
||||
bottom: sliderBottomPx,
|
||||
opacity: sliderLeft === 4 && sliderLeft === sliderRight ? 0 : activeIndex === -1 ? 0 : 1,
|
||||
}"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const route = useNativeRoute();
|
||||
|
||||
const props = defineProps({
|
||||
links: {
|
||||
default: () => [],
|
||||
type: Array,
|
||||
},
|
||||
query: {
|
||||
default: null,
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
interface Tab {
|
||||
label: string;
|
||||
href: string;
|
||||
shown?: boolean;
|
||||
icon?: string;
|
||||
subpages?: string[];
|
||||
}
|
||||
|
||||
const sliderPositionX = ref(0);
|
||||
const sliderPositionY = ref(0);
|
||||
const selectedElementWidth = ref(0);
|
||||
const props = defineProps<{
|
||||
links: Tab[];
|
||||
query?: string;
|
||||
}>();
|
||||
|
||||
const sliderLeft = ref(4);
|
||||
const sliderTop = ref(4);
|
||||
const sliderRight = ref(4);
|
||||
const sliderBottom = ref(4);
|
||||
const activeIndex = ref(-1);
|
||||
const oldIndex = ref(-1);
|
||||
const subpageSelected = ref(false);
|
||||
|
||||
const filteredLinks = computed(() =>
|
||||
props.links.filter((x) => (x.shown === undefined ? true : x.shown)),
|
||||
);
|
||||
const positionToMoveX = computed(() => `${sliderPositionX.value}px`);
|
||||
const positionToMoveY = computed(() => `${sliderPositionY.value}px`);
|
||||
const sliderWidth = computed(() => `${selectedElementWidth.value}px`);
|
||||
const sliderLeftPx = computed(() => `${sliderLeft.value}px`);
|
||||
const sliderTopPx = computed(() => `${sliderTop.value}px`);
|
||||
const sliderRightPx = computed(() => `${sliderRight.value}px`);
|
||||
const sliderBottomPx = computed(() => `${sliderBottom.value}px`);
|
||||
|
||||
function pickLink() {
|
||||
let index = -1;
|
||||
subpageSelected.value = false;
|
||||
for (let i = filteredLinks.value.length - 1; i >= 0; i--) {
|
||||
if (decodeURIComponent(route.path).includes(filteredLinks.value[i].href)) {
|
||||
const link = filteredLinks.value[i];
|
||||
if (decodeURIComponent(route.path) === link.href) {
|
||||
index = i;
|
||||
break;
|
||||
} else if (
|
||||
decodeURIComponent(route.path).includes(link.href) ||
|
||||
(link.subpages &&
|
||||
link.subpages.some((subpage) => decodeURIComponent(route.path).includes(subpage)))
|
||||
) {
|
||||
index = i;
|
||||
subpageSelected.value = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
activeIndex.value = index;
|
||||
@@ -66,19 +87,57 @@ function pickLink() {
|
||||
startAnimation();
|
||||
} else {
|
||||
oldIndex.value = -1;
|
||||
sliderPositionX.value = 0;
|
||||
selectedElementWidth.value = 0;
|
||||
sliderLeft.value = 0;
|
||||
sliderRight.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const linkElements = ref();
|
||||
const tabLinkElements = ref();
|
||||
|
||||
function startAnimation() {
|
||||
const el = linkElements.value[activeIndex.value].$el;
|
||||
const el = tabLinkElements.value[activeIndex.value].$el;
|
||||
|
||||
sliderPositionX.value = el.offsetLeft;
|
||||
sliderPositionY.value = el.offsetTop;
|
||||
selectedElementWidth.value = el.offsetWidth;
|
||||
if (!el || !el.offsetParent) return;
|
||||
|
||||
const newValues = {
|
||||
left: el.offsetLeft,
|
||||
top: el.offsetTop,
|
||||
right: el.offsetParent.offsetWidth - el.offsetLeft - el.offsetWidth,
|
||||
bottom: el.offsetParent.offsetHeight - el.offsetTop - el.offsetHeight,
|
||||
};
|
||||
|
||||
if (sliderLeft.value === 4 && sliderRight.value === 4) {
|
||||
sliderLeft.value = newValues.left;
|
||||
sliderRight.value = newValues.right;
|
||||
sliderTop.value = newValues.top;
|
||||
sliderBottom.value = newValues.bottom;
|
||||
} else {
|
||||
const delay = 200;
|
||||
|
||||
if (newValues.left < sliderLeft.value) {
|
||||
sliderLeft.value = newValues.left;
|
||||
setTimeout(() => {
|
||||
sliderRight.value = newValues.right;
|
||||
}, delay);
|
||||
} else {
|
||||
sliderRight.value = newValues.right;
|
||||
setTimeout(() => {
|
||||
sliderLeft.value = newValues.left;
|
||||
}, delay);
|
||||
}
|
||||
|
||||
if (newValues.top < sliderTop.value) {
|
||||
sliderTop.value = newValues.top;
|
||||
setTimeout(() => {
|
||||
sliderBottom.value = newValues.bottom;
|
||||
}, delay);
|
||||
} else {
|
||||
sliderBottom.value = newValues.bottom;
|
||||
setTimeout(() => {
|
||||
sliderTop.value = newValues.top;
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -92,3 +151,11 @@ onUnmounted(() => {
|
||||
|
||||
watch(route, () => pickLink());
|
||||
</script>
|
||||
<style scoped>
|
||||
.navtabs-transition {
|
||||
/* Delay on opacity is to hide any jankiness as the page loads */
|
||||
transition:
|
||||
all 150ms cubic-bezier(0.4, 0, 0.2, 1) 0s,
|
||||
opacity 250ms cubic-bezier(0.5, 0, 0.2, 1) 50ms;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user