refactor: ♻️ Replace HeadlessUI with Ark UI, improve UI

This commit is contained in:
Jesse Wierzbinski 2024-06-04 14:03:15 -10:00
parent d109e09a72
commit 3c68c2e788
No known key found for this signature in database
15 changed files with 231 additions and 242 deletions

View file

@ -0,0 +1,57 @@
<template>
<HeadlessTransitionRoot as="template" :show="lightbox">
<Dialog.Root v-model:open="lightbox" :close-on-escape="true" :close-on-interact-outside="true"
@update:open="o => lightbox = o">
<Teleport to="body">
<Dialog.Positioner class="z-50">
<HeadlessTransitionChild as="template" enter="ease-out duration-200" enter-from="opacity-0"
enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100"
leave-to="opacity-0">
<Dialog.Backdrop class="fixed inset-0 bg-black/70 !z-40" @click="lightbox = false" />
</HeadlessTransitionChild>
<Dialog.Content
class="w-screen h-screen flex !z-50 justify-center items-center flex-col overflow-hidden p-10 fixed inset-0">
<div class="w-full absolute inset-x-0 top-0 p-10 shrink text-gray-400 flex flex-row gap-3">
<a @click.stop :href="attachment?.url" target="_blank" download class="ml-auto">
<iconify-icon icon="tabler:download" width="1.5rem" height="1.5rem" />
<span class="sr-only">Close</span>
</a>
<button @click.stop="lightbox = false">
<iconify-icon icon="tabler:x" width="1.5rem" height="1.5rem" />
<span class="sr-only">Close</span>
</button>
</div>
<HeadlessTransitionChild as="template" enter="ease-out duration-200"
enter-from="opacity-0 sm:scale-95" enter-to="opacity-100 sm:scale-100"
leave="ease-in duration-200" leave-from="opacity-100 sm:scale-100"
leave-to="opacity-0 sm:scale-95">
<img @click.stop v-if="attachment?.type === 'image'"
class="rounded max-w-full min-w-[30%] max-h-[70%]" :src="attachment.url"
:alt="attachment.description ?? ''" :title="attachment.description ?? ''" />
</HeadlessTransitionChild>
<span @click.stop v-if="attachment?.description"
class="text-gray-300 rounded mt-6 -mb-20 px-4 py-2 max-w-xl ring-1 ring-white/5 bg-dark-900 max-h-40 overflow-y-auto">
{{ attachment.description }}
</span>
</Dialog.Content>
</Dialog.Positioner>
</Teleport>
</Dialog.Root>
</HeadlessTransitionRoot>
</template>t
<script lang="ts" setup>
import { Dialog } from "@ark-ui/vue";
import type { Attachment } from "~/types/mastodon/attachment";
const lightbox = ref(false);
const attachment = ref<Attachment | null>(null);
useListen("attachment:view", async (a) => {
attachment.value = a;
await nextTick();
lightbox.value = true;
});
</script>

View file

@ -1,55 +1,26 @@
<template>
<div @click="lightbox = true" tabindex="0" aria-label="Open attachment in lightbox" @keydown="lightbox = true"
<div tabindex="0" aria-label="Open attachment in lightbox"
class="aspect-video w-full rounded ring-white/5 shadow overflow-hidden ring-1 hover:ring-2 duration-100">
<img v-if="attachment.type === 'image'" tabindex="-1"
class="object-cover w-full h-full rounded duration-150 hover:scale-[102%] ease-in-out" :src="attachment.url"
:alt="attachment.description ?? ''" :title="attachment.description ?? ''" />
:alt="attachment.description ?? ''" :title="attachment.description ?? ''" @click="openLightbox"
@keydown="openLightbox" />
<video v-else-if="attachment.type === 'video'" class="object-cover w-full h-full rounded" controls
:alt="attachment.description ?? ''" :title="attachment.description ?? ''">
<source :src="attachment.url" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
<HeadlessTransitionRoot appear :show="lightbox" as="template">
<HeadlessDialog @close="lightbox = false">
<div class="fixed inset-0 overflow-y-auto z-50 bg-black/70">
<div class="flex min-h-full items-center justify-center text-center">
<HeadlessTransitionChild as="template" enter="duration-100 ease-out" enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100">
<HeadlessDialogPanel
class="w-screen h-screen flex justify-center items-center flex-col relative overflow-hidden p-10"
@click="lightbox = false">
<div class="w-full absolute inset-x-0 top-0 p-10 shrink text-gray-400 flex flex-row gap-3">
<a @click.stop :href="attachment.url" target="_blank" download class="ml-auto">
<iconify-icon icon="tabler:download" width="1.5rem" height="1.5rem" />
<span class="sr-only">Close</span>
</a>
<button @click.stop="lightbox = false">
<iconify-icon icon="tabler:x" width="1.5rem" height="1.5rem" />
<span class="sr-only">Close</span>
</button>
</div>
<img @click.stop v-if="attachment.type === 'image'"
class="rounded max-w-full min-w-[30%] max-h-[70%]" :src="attachment.url"
:alt="attachment.description ?? ''" :title="attachment.description ?? ''" />
<span @click.stop v-if="attachment.description"
class="text-gray-300 rounded mt-6 -mb-20 px-4 py-2 max-w-xl ring-1 ring-white/5 bg-dark-900 max-h-40 overflow-y-auto">
{{ attachment.description }}
</span>
</HeadlessDialogPanel>
</HeadlessTransitionChild>
</div>
</div>
</HeadlessDialog>
</HeadlessTransitionRoot>
</template>
<script lang="ts" setup>
import type { Attachment } from "~/types/mastodon/attachment";
const lightbox = ref(false);
defineProps<{
const props = defineProps<{
attachment: Attachment;
}>();
const openLightbox = () => {
useEvent("attachment:view", props.attachment);
};
</script>

View file

@ -24,17 +24,17 @@
class="text-gray-200 group-hover:group-enabled:text-blue-600" aria-hidden="true" />
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(note?.replies_count) }}</span>
</button>
<button class="group" :disabled="!isSignedIn">
<button class="group" @click="likeFn" :disabled="!isSignedIn">
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:heart" v-if="!note?.favourited"
class="size-5 text-gray-200 group-hover:group-enabled:text-pink-600" aria-hidden="true" />
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:heart-filled" v-else
class="size-5 text-pink-600 group-hover:group-enabled:text-gray-200" aria-hidden="true" />
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(note?.favourites_count) }}</span>
</button>
<button class="group" :disabled="!isSignedIn">
<button class="group" @click="reblogFn" :disabled="!isSignedIn">
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:repeat" v-if="!note?.reblogged"
class="size-5 text-gray-200 group-hover:group-enabled:text-green-600" aria-hidden="true" />
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:repeat-off" v-else
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:repeat" v-else
class="size-5 text-green-600 group-hover:group-enabled:text-gray-200" aria-hidden="true" />
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(note?.reblogs_count) }}</span>
</button>
@ -45,32 +45,30 @@
</button>
<DropdownsAdaptiveDropdown>
<template #button>
<HeadlessMenuButton>
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:dots"
class="size-5 text-gray-200" aria-hidden="true" />
<span class="sr-only">Open menu</span>
</HeadlessMenuButton>
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:dots" class="size-5 text-gray-200"
aria-hidden="true" />
<span class="sr-only">Open menu</span>
</template>
<template #items>
<HeadlessMenuItem>
<Menu.Item value="">
<ButtonsDropdownElement @click="copy(JSON.stringify(note, null, 4))" icon="tabler:code"
class="w-full">
Copy API
Response
</ButtonsDropdownElement>
</HeadlessMenuItem>
<HeadlessMenuItem>
</Menu.Item>
<Menu.Item value="">
<ButtonsDropdownElement @click="copy(url)" icon="tabler:link" class="w-full">
Copy Link
</ButtonsDropdownElement>
</HeadlessMenuItem>
<HeadlessMenuItem>
</Menu.Item>
<Menu.Item value="">
<ButtonsDropdownElement @click="remove" icon="tabler:backspace" :disabled="!isSignedIn"
class="w-full border-r-2 border-red-500">
Delete
</ButtonsDropdownElement>
</HeadlessMenuItem>
</Menu.Item>
</template>
</DropdownsAdaptiveDropdown>
</div>
@ -79,6 +77,7 @@
</template>
<script lang="ts" setup>
import { Menu } from "@ark-ui/vue";
import Skeleton from "~/components/skeleton/Skeleton.vue";
import type { Status } from "~/types/mastodon/status";
@ -94,6 +93,8 @@ const props = withDefaults(
},
);
const noteRef = ref(props.note);
const tokenData = useTokenData();
const isSignedIn = useSignedIn();
const client = useMegalodon(tokenData);
@ -108,7 +109,7 @@ const {
reblog,
isReply,
reblogDisplayName,
} = useNoteData(ref(props.note), client);
} = useNoteData(noteRef, client);
const { copy } = useClipboard();
const numberFormat = (number = 0) =>
@ -117,4 +118,38 @@ const numberFormat = (number = 0) =>
compactDisplay: "short",
maximumFractionDigits: 1,
}).format(number);
const likeFn = async () => {
if (!note.value) return;
if (note.value.favourited) {
const output = await client.value?.unfavouriteStatus(note.value.id);
if (output?.data) {
noteRef.value = output.data;
}
} else {
const output = await client.value?.favouriteStatus(note.value.id);
if (output?.data) {
noteRef.value = output.data;
}
}
};
const reblogFn = async () => {
if (!note.value) return;
if (note.value?.reblogged) {
const output = await client.value?.unreblogStatus(note.value.id);
if (output?.data) {
noteRef.value = output.data;
}
} else {
const output = await client.value?.reblogStatus(note.value.id);
if (output?.data.reblog) {
noteRef.value = output.data.reblog;
}
}
};
</script>