feat: Add bottom sidebar on mobile

This commit is contained in:
Jesse Wierzbinski 2024-04-30 22:40:33 -10:00
parent 951a806477
commit d9173b4ce2
No known key found for this signature in database
11 changed files with 187 additions and 66 deletions

View file

@ -1,5 +1,5 @@
<template> <template>
<HeadlessMenu v-slot="{ close }"> <HeadlessMenu v-slot="{ close }" v-bind="$props">
<slot name="button"></slot> <slot name="button"></slot>
<HeadlessMenuItems @click="close" class="fixed z-20 inset-0 z-5 bg-black/50"> <HeadlessMenuItems @click="close" class="fixed z-20 inset-0 z-5 bg-black/50">
@ -25,5 +25,5 @@
<script setup lang="ts"> <script setup lang="ts">
const { width } = useWindowSize(); const { width } = useWindowSize();
const isSmallScreen = computed(() => width.value < 640); const isSmallScreen = computed(() => width.value < 768);
</script> </script>

View file

@ -8,13 +8,15 @@
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<h3 class="font-semibold text-gray-300 text-xs uppercase opacity-0 group-hover:opacity-100 duration-200"> <h3 class="font-semibold text-gray-300 text-xs uppercase opacity-0 group-hover:opacity-100 duration-200">
Timelines</h3> Timelines</h3>
<NuxtLink v-for="timeline in timelines" :key="timeline.href" :to="timeline.href"> <ClientOnly>
<ButtonsBase v-if="!timeline.requiresAuth || (timeline.requiresAuth && tokenData)" <NuxtLink v-for="timeline in visibleTimelines" :key="timeline.href" :to="timeline.href">
class="flex flex-row text-left items-center justify-start gap-3 text-lg hover:ring-1 ring-white/10 overflow-hidden h-12 w-full duration-200"> <ButtonsBase
<Icon :name="timeline.icon" class="shrink-0 text-2xl" /> class="flex flex-row text-left items-center justify-start gap-3 text-lg hover:ring-1 ring-white/10 overflow-hidden h-12 w-full duration-200">
<span class="pr-28 line-clamp-1">{{ timeline.name }}</span> <Icon :name="timeline.icon" class="shrink-0 text-2xl" />
</ButtonsBase> <span class="pr-28 line-clamp-1">{{ timeline.name }}</span>
</NuxtLink> </ButtonsBase>
</NuxtLink>
</ClientOnly>
</div> </div>
<div class="flex flex-col gap-3 mt-auto"> <div class="flex flex-col gap-3 mt-auto">
@ -50,6 +52,67 @@
</ClientOnly> </ClientOnly>
</div> </div>
</aside> </aside>
<!-- Mobile bottom navbar -->
<nav
class="fixed bottom-0 left-0 right-0 z-20 md:hidden flex justify-around p-2 *:shadow-xl bg-dark-900 ring-1 ring-white/10 text-gray-200">
<DropdownsAdaptiveDropdown>
<template #button>
<HeadlessMenuButton class="flex flex-col items-center justify-center p-2 rounded">
<Icon name="tabler:home" class="text-2xl" />
<span class="text-xs">Timelines</span>
</HeadlessMenuButton>
</template>
<template #items>
<HeadlessMenuItem v-for="timeline in visibleTimelines" :key="timeline.href" :href="timeline.href">
<NuxtLink>
<ButtonsDropdownElement :icon="timeline.icon" class="w-full">
{{ timeline.name }}
</ButtonsDropdownElement>
</NuxtLink>
</HeadlessMenuItem>
</template>
</DropdownsAdaptiveDropdown>
<NuxtLink href="/notifications" class="flex flex-col items-center justify-center p-2 rounded">
<Icon name="tabler:bell" class="text-2xl" />
<span class="text-xs">Notifications</span>
</NuxtLink>
<DropdownsAdaptiveDropdown>
<template #button>
<HeadlessMenuButton class="flex flex-col items-center justify-center p-2 rounded">
<Icon name="tabler:user" class="text-2xl" />
<span class="text-xs">Account</span>
</HeadlessMenuButton>
</template>
<template #items>
<HeadlessMenuItem v-if="tokenData">
<ButtonsDropdownElement icon="tabler:logout" class="w-full"
@click="signOut().finally(() => loadingAuth = false)" :loading="loadingAuth">
Sign Out
</ButtonsDropdownElement>
</HeadlessMenuItem>
<HeadlessMenuItem v-if="!tokenData">
<ButtonsDropdownElement icon="tabler:login" class="w-full"
@click="signIn().finally(() => loadingAuth = false)" :loading="loadingAuth">
Sign In
</ButtonsDropdownElement>
</HeadlessMenuItem>
<HeadlessMenuItem v-if="!tokenData">
<NuxtLink href="/register">
<ButtonsDropdownElement icon="tabler:certificate" class="w-full">
Register
</ButtonsDropdownElement>
</NuxtLink>
</HeadlessMenuItem>
</template>
</DropdownsAdaptiveDropdown>
<button @click="compose" v-if="tokenData"
class="flex flex-col items-center justify-center p-2 rounded bg-gradient-to-tr from-pink-300/70 via-purple-300/70 to-indigo-400/70">
<Icon name="tabler:writing" class="text-2xl" />
<span class="text-xs">Compose</span>
</button>
</nav>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -72,6 +135,12 @@ const timelines = ref([
}, },
]); ]);
const visibleTimelines = computed(() =>
timelines.value.filter(
(timeline) => !timeline.requiresAuth || tokenData.value,
),
);
const loadingAuth = ref(false); const loadingAuth = ref(false);
const appData = useAppData(); const appData = useAppData();
@ -131,7 +200,7 @@ const signOut = async () => {
tokenData.value.access_token, tokenData.value.access_token,
tokenData.value.access_token, tokenData.value.access_token,
) )
.catch(() => { }); .catch(() => {});
tokenData.value = null; tokenData.value = null;
me.value = null; me.value = null;

View file

@ -0,0 +1,23 @@
<template>
<slot />
</template>
<script lang="ts" setup>
const root = useParentElement();
// Store and keep y to restore it on page change
const route = useRoute();
const yStored = useLocalStorage(`lysand:scroll-${route.fullPath}`, 0);
const { y } = useScroll(root);
useEventListener("popstate", async (event) => {
if (yStored.value !== null) {
// Wait for the Vue component to load
await new Promise((resolve) => setTimeout(resolve, 100));
y.value = yStored.value;
}
});
onBeforeRouteLeave(() => {
yStored.value = y.value;
});
</script>

View file

@ -2,10 +2,10 @@
<div class="from-dark-600 to-dark-900 bg-gradient-to-tl min-h-dvh"> <div class="from-dark-600 to-dark-900 bg-gradient-to-tl min-h-dvh">
<SidebarsNavigation /> <SidebarsNavigation />
<div class="relative md:pl-20 min-h-dvh flex flex-row justify-center lg:justify-between"> <div class="relative md:pl-20 min-h-dvh flex flex-row justify-center lg:justify-between">
<aside <aside v-if="width > 1024"
class="max-w-md max-h-dvh overflow-y-auto w-full bg-dark-900 ring-1 ring-white/10 hidden lg:flex flex-col gap-10"> class="max-w-md max-h-dvh overflow-y-auto w-full bg-dark-900 ring-1 ring-white/10 hidden lg:flex flex-col">
<ClientOnly> <ClientOnly>
<div class="grow p-10" v-if="!tokenData"> <div class="grow p-10 mb-10" v-if="!tokenData">
<button type="button" <button type="button"
class="relative block h-full w-full rounded-lg border-2 border-dashed border-dark-300 p-12 text-center"> class="relative block h-full w-full rounded-lg border-2 border-dashed border-dark-300 p-12 text-center">
<Icon name="tabler:notification" class="mx-auto h-12 w-12 text-gray-400" /> <Icon name="tabler:notification" class="mx-auto h-12 w-12 text-gray-400" />
@ -16,37 +16,38 @@
sign in</span> sign in</span>
</button> </button>
</div> </div>
<div class="grow" v-else> <TimelinesTimelineScroller v-else>
<TimelinesNotifications /> <TimelinesNotifications />
</TimelinesTimelineScroller>
<div class="mt-auto prose prose-invert prose-sm flex flex-col gap-4 px-10 pb-10" v-if="!tokenData">
<div class="text-center">
<strong
class="bg-gradient-to-tr from-pink-300 via-purple-300 to-indigo-400 text-transparent bg-clip-text">Lysand
{{ instance?.lysand_version ?? instance?.version }}</strong> <a
href="https://github.com/lysand-org/lysand" target="_blank">Source Code</a> <a
href="https://github.com/lysand-org/lysand/issues" target="_blank">Report an Issue</a>
</div>
<NuxtLink href="https://github.com/lysand-org/lysand" target="_blank">
<ButtonsSecondary class="w-full">
Create your own instance
</ButtonsSecondary>
</NuxtLink>
<NuxtLink href="/about/apps">
<ButtonsSecondary class="w-full">
Mobile Apps
</ButtonsSecondary>
</NuxtLink>
</div> </div>
</ClientOnly> </ClientOnly>
<div class="mt-auto prose prose-invert prose-sm flex flex-col gap-4 px-10 pb-10">
<div class="text-center">
<strong
class="bg-gradient-to-tr from-pink-300 via-purple-300 to-indigo-400 text-transparent bg-clip-text">Lysand
{{ instance?.lysand_version ?? instance?.version }}</strong> <a
href="https://github.com/lysand-org/lysand" target="_blank">Source Code</a> <a
href="https://github.com/lysand-org/lysand/issues" target="_blank">Report an Issue</a>
</div>
<NuxtLink href="https://github.com/lysand-org/lysand" target="_blank">
<ButtonsSecondary class="w-full">
Create your own instance
</ButtonsSecondary>
</NuxtLink>
<NuxtLink href="/about/apps">
<ButtonsSecondary class="w-full">
Mobile Apps
</ButtonsSecondary>
</NuxtLink>
</div>
</aside> </aside>
<div class="w-full max-h-dvh overflow-y-auto"> <div class="w-full max-h-dvh">
<slot /> <slot />
</div> </div>
<aside class="max-w-md max-h-dvh overflow-y-auto w-full bg-dark-900 ring-1 ring-white/10 lg:block hidden"> <aside v-if="width > 1024"
class="max-w-md max-h-dvh overflow-y-auto w-full bg-dark-900 ring-1 ring-white/10 lg:block hidden">
<slot name="right"> <slot name="right">
<SocialElementsInstancePresentation /> <SocialElementsInstancePresentation />
</slot> </slot>
@ -63,6 +64,7 @@ const tokenData = useTokenData();
const client = useMegalodon(tokenData); const client = useMegalodon(tokenData);
const instance = useInstance(client); const instance = useInstance(client);
const description = useExtendedDescription(client); const description = useExtendedDescription(client);
const { width } = useWindowSize();
useServerSeoMeta({ useServerSeoMeta({
title: instance.value?.title, title: instance.value?.title,

View file

@ -22,8 +22,8 @@ export default defineNuxtConfig({
], ],
htmlAttrs: { lang: "en-us" }, htmlAttrs: { lang: "en-us" },
}, },
keepalive: true,
}, },
/* shiki: { /* shiki: {
defaultTheme: "rose-pine", defaultTheme: "rose-pine",
bundledLangs: [ bundledLangs: [
@ -42,7 +42,6 @@ export default defineNuxtConfig({
"yaml", "yaml",
], ],
}, */ }, */
nitro: { nitro: {
preset: "bun", preset: "bun",
minify: true, minify: true,
@ -54,28 +53,12 @@ export default defineNuxtConfig({
gzip: false, gzip: false,
}, },
}, },
schemaOrg: { schemaOrg: {
enabled: false, enabled: false,
}, },
ogImage: { ogImage: {
enabled: false, enabled: false,
}, },
vite: {
define: {
__VERSION__: JSON.stringify("0.4"),
},
server: {
hmr: {
clientPort: 3000,
host: "localhost",
protocol: "ws",
},
},
},
veeValidate: { veeValidate: {
autoImports: true, autoImports: true,
componentNames: { componentNames: {
@ -85,7 +68,6 @@ export default defineNuxtConfig({
ErrorMessage: "VeeErrorMessage", ErrorMessage: "VeeErrorMessage",
}, },
}, },
runtimeConfig: { runtimeConfig: {
public: { public: {
language: "en-US", language: "en-US",
@ -95,11 +77,9 @@ export default defineNuxtConfig({
apiHost: "https://social.lysand.org", apiHost: "https://social.lysand.org",
}, },
}, },
site: { site: {
url: "https://social.lysand.org", url: "https://social.lysand.org",
}, },
devtools: { devtools: {
enabled: true, enabled: true,
}, },

View file

@ -1,8 +1,11 @@
<template> <template>
<NuxtLayout name="app"> <NuxtLayout name="app">
<SocialElementsUsersAccount v-if="isMobile" :account="account ?? undefined" /> <div class="max-h-dvh overflow-y-scroll">
<TimelinesAccount :id="accountId ?? undefined" :key="accountId ?? undefined" />
<TimelinesTimelineScroller>
<TimelinesAccount :id="accountId ?? undefined" :key="accountId ?? undefined" />
</TimelinesTimelineScroller>
</div>
<template #right> <template #right>
<SocialElementsUsersAccount v-if="!isMobile" :account="account ?? undefined" /> <SocialElementsUsersAccount v-if="!isMobile" :account="account ?? undefined" />
<div v-else> <div v-else>
@ -17,6 +20,7 @@ import type { Account } from "~/types/mastodon/account";
definePageMeta({ definePageMeta({
layout: false, layout: false,
keepalive: true,
}); });
const { width } = useWindowSize(); const { width } = useWindowSize();
@ -32,7 +36,7 @@ const account = computed<Account | null>(
); );
const accountId = computed(() => account.value?.id ?? null); const accountId = computed(() => account.value?.id ?? null);
useServerSeoMeta({ useSeoMeta({
title: account.value?.display_name, title: account.value?.display_name,
description: account.value?.note, description: account.value?.note,
ogImage: account.value?.avatar, ogImage: account.value?.avatar,

View file

@ -1,5 +1,9 @@
<template> <template>
<TimelinesHome /> <div class="max-h-dvh overflow-y-auto">
<TimelinesTimelineScroller>
<TimelinesHome />
</TimelinesTimelineScroller>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View file

@ -1,5 +1,9 @@
<template> <template>
<TimelinesPublic /> <div class="max-h-dvh overflow-y-scroll">
<TimelinesTimelineScroller>
<TimelinesPublic />
</TimelinesTimelineScroller>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View file

@ -1,5 +1,9 @@
<template> <template>
<TimelinesLocal /> <div class="max-h-dvh overflow-y-auto">
<TimelinesTimelineScroller>
<TimelinesLocal />
</TimelinesTimelineScroller>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

27
pages/notifications.vue Normal file
View file

@ -0,0 +1,27 @@
<template>
<ClientOnly>
<div class="max-h-dvh overflow-y-auto">
<div class="shrink-0 p-10 h-dvh" v-if="!tokenData">
<button type="button"
class="relative block h-full w-full rounded-lg border-2 border-dashed border-dark-300 p-12 text-center">
<Icon name="tabler:notification" class="mx-auto h-12 w-12 text-gray-400" />
<span class="mt-3 block text-sm font-semibold text-gray-200 max-w-56 mx-auto">Notifications
will
appear here
when you
sign in</span>
</button>
</div>
<TimelinesTimelineScroller v-else>
<TimelinesNotifications />
</TimelinesTimelineScroller>
</div>
</ClientOnly>
</template>
<script lang="ts" setup>
definePageMeta({
layout: "app",
});
const tokenData = useTokenData();
</script>

View file

@ -1,5 +1,9 @@
<template> <template>
<TimelinesPublic /> <div class="max-h-dvh overflow-y-auto">
<TimelinesTimelineScroller>
<TimelinesPublic />
</TimelinesTimelineScroller>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">