mirror of
https://github.com/versia-pub/frontend.git
synced 2026-03-13 11:39:16 +01:00
refactor: ♻️ Replace HeadlessUI with Ark UI, improve UI
This commit is contained in:
parent
d109e09a72
commit
3c68c2e788
15 changed files with 231 additions and 242 deletions
57
components/social-elements/notes/attachment-dialog.vue
Normal file
57
components/social-elements/notes/attachment-dialog.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue