mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 08:28:20 +01:00
feat: ✨ Add emoji viewer settings
This commit is contained in:
parent
3d27565a3e
commit
28facd6d5f
50
components/settings/emojis/category.vue
Normal file
50
components/settings/emojis/category.vue
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="grid grid-cols-[1fr,auto,auto] gap-4 items-baseline">
|
||||||
|
<h2 class="text-xl font-bold">{{ name }}</h2>
|
||||||
|
<!-- <Button theme="primary">
|
||||||
|
<Icon icon="tabler:upload" />
|
||||||
|
<span class="hidden md:block">New</span>
|
||||||
|
</Button> -->
|
||||||
|
<Button theme="outline">
|
||||||
|
<Icon icon="tabler:chevron-up" class="duration-100" :style="{
|
||||||
|
transform: collapsed ? 'rotate(180deg)' : 'rotate(0deg)',
|
||||||
|
}" @click="collapsed = !collapsed" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div ref="container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 overflow-hidden duration-200">
|
||||||
|
<GridItem v-for="emoji in emojis" :key="emoji.id" :emoji="emoji" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Emoji } from "@versia/client/types";
|
||||||
|
import Button from "~/packages/ui/components/buttons/button.vue";
|
||||||
|
import Icon from "~/packages/ui/components/icons/icon.vue";
|
||||||
|
import GridItem from "./grid-item.vue";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
emojis: (Emoji & { id: string; global: boolean })[];
|
||||||
|
name: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const collapsed = ref(false);
|
||||||
|
const container = ref<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
collapsed,
|
||||||
|
(value) => {
|
||||||
|
// Use requestAnimationFrame to prevent layout thrashing
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (!container.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
container.value.style.maxHeight = value
|
||||||
|
? "0px"
|
||||||
|
: `${container.value.scrollHeight}px`;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
59
components/settings/emojis/emojis.vue
Normal file
59
components/settings/emojis/emojis.vue
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<div class="max-w-7xl mx-auto my-12 px-4">
|
||||||
|
<div class="md:max-w-sm w-full relative mb-4">
|
||||||
|
<TextInput v-model="search" placeholder="Search" class="pl-8" />
|
||||||
|
<iconify-icon icon="tabler:search"
|
||||||
|
class="absolute size-4 top-1/2 left-2.5 transform -translate-y-1/2 text-gray-200" aria-hidden="true"
|
||||||
|
width="unset" />
|
||||||
|
</div>
|
||||||
|
<Category v-if="emojis.length > 0" v-for="([name, emojis]) in categories" :key="name" :emojis="emojis"
|
||||||
|
:name="name" />
|
||||||
|
<div v-else class="flex flex-col items-center justify-center gap-2 text-gray-200 text-center p-10">
|
||||||
|
<span class="text-lg font-semibold">No emojis found.</span>
|
||||||
|
<span class="text-sm">
|
||||||
|
You can ask your administrator to add some emojis.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Emoji } from "@versia/client/types";
|
||||||
|
import TextInput from "~/components/inputs/text-input.vue";
|
||||||
|
import Category from "./category.vue";
|
||||||
|
|
||||||
|
const emojis = computed(() =>
|
||||||
|
(
|
||||||
|
(identity.value?.emojis as
|
||||||
|
| (Emoji & { id: string; global: boolean })[]
|
||||||
|
| undefined) ?? []
|
||||||
|
).filter((emoji) =>
|
||||||
|
emoji.shortcode.toLowerCase().includes(search.value.toLowerCase()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const search = ref("");
|
||||||
|
|
||||||
|
const categories = computed(() => {
|
||||||
|
const categories = new Map<
|
||||||
|
string,
|
||||||
|
(Emoji & { id: string; global: boolean })[]
|
||||||
|
>();
|
||||||
|
for (const emoji of emojis.value) {
|
||||||
|
if (!emoji.category) {
|
||||||
|
if (!categories.has("Uncategorized")) {
|
||||||
|
categories.set("Uncategorized", []);
|
||||||
|
}
|
||||||
|
|
||||||
|
categories.get("Uncategorized")?.push(emoji);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!categories.has(emoji.category)) {
|
||||||
|
categories.set(emoji.category, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
categories.get(emoji.category)?.push(emoji);
|
||||||
|
}
|
||||||
|
return categories;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
33
components/settings/emojis/grid-item-menu.vue
Normal file
33
components/settings/emojis/grid-item-menu.vue
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<AdaptiveDropdown>
|
||||||
|
<template #button>
|
||||||
|
<Button theme="outline">
|
||||||
|
<iconify-icon width="none" icon="tabler:dots" class="size-5 text-gray-200"
|
||||||
|
aria-hidden="true" />
|
||||||
|
<span class="sr-only">Open menu</span>
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #items>
|
||||||
|
<Menu.ItemGroup>
|
||||||
|
<Menu.Item value="">
|
||||||
|
<ButtonDropdown icon="tabler:trash" class="w-full">
|
||||||
|
Delete
|
||||||
|
</ButtonDropdown>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.ItemGroup>
|
||||||
|
</template>
|
||||||
|
</AdaptiveDropdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Menu } from "@ark-ui/vue";
|
||||||
|
import type { Emoji } from "@versia/client/types";
|
||||||
|
import ButtonDropdown from "~/components/buttons/button-dropdown.vue";
|
||||||
|
import AdaptiveDropdown from "~/components/dropdowns/AdaptiveDropdown.vue";
|
||||||
|
import Button from "~/packages/ui/components/buttons/button.vue";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
emoji: Emoji & { id: string; global: boolean };
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
17
components/settings/emojis/grid-item.vue
Normal file
17
components/settings/emojis/grid-item.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<div class="rounded ring-1 m-1 ring-white/10 grid grid-cols-[auto,1fr] gap-x-4 p-3 bg-dark-400 hover:ring-2 hover:ring-primary-600 duration-100 items-center">
|
||||||
|
<Avatar :src="emoji.url" class="size-12 rounded bg-transparent" />
|
||||||
|
<div class="text-ellipsis font-mono text-wrap w-full overflow-hidden">{{ emoji.shortcode }}</div>
|
||||||
|
<!-- <GridItemMenu :emoji="emoji" /> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Emoji } from "@versia/client/types";
|
||||||
|
import Avatar from "~/components/avatars/avatar.vue";
|
||||||
|
import GridItemMenu from "./grid-item-menu.vue";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
emoji: Emoji & { id: string; global: boolean };
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<Tabs.Root v-model="tab">
|
<Tabs.Root v-model="tab">
|
||||||
<Tabs.List class="flex flex-row p-4 gap-4 bg-dark-800 relative ring-1 ring-white/5 overflow-x-auto">
|
<Tabs.List class="flex flex-row p-4 gap-4 bg-dark-800 relative ring-1 ring-white/5 overflow-x-auto">
|
||||||
<Tabs.Trigger :value="page" v-for="page of [SettingPages.Account, SettingPages.Behaviour, SettingPages.Appearance]" :as-child="true">
|
<Tabs.Trigger :value="page" v-for="page of [SettingPages.Account, SettingPages.Emojis, SettingPages.Behaviour, SettingPages.Appearance]" :as-child="true">
|
||||||
<ButtonBase class="capitalize hover:bg-white/5">
|
<ButtonBase class="capitalize hover:bg-white/5">
|
||||||
{{ page }}
|
{{ page }}
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
|
|
|
||||||
12
package.json
12
package.json
|
|
@ -29,11 +29,11 @@
|
||||||
"check": "bunx tsc -p ."
|
"check": "bunx tsc -p ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ark-ui/vue": "^4.2.0",
|
"@ark-ui/vue": "^4.4.1",
|
||||||
"@nuxt/fonts": "^0.10.2",
|
"@nuxt/fonts": "^0.10.2",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@vee-validate/nuxt": "^4.14.6",
|
"@vee-validate/nuxt": "^4.14.7",
|
||||||
"@vee-validate/zod": "^4.14.6",
|
"@vee-validate/zod": "^4.14.7",
|
||||||
"@versia/client": "^0.1.0",
|
"@versia/client": "^0.1.0",
|
||||||
"@vite-pwa/nuxt": "^0.10.6",
|
"@vite-pwa/nuxt": "^0.10.6",
|
||||||
"@vueuse/core": "^11.2.0",
|
"@vueuse/core": "^11.2.0",
|
||||||
|
|
@ -45,13 +45,13 @@
|
||||||
"magic-regexp": "^0.8.0",
|
"magic-regexp": "^0.8.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"nanoid": "^5.0.8",
|
"nanoid": "^5.0.8",
|
||||||
"nuxt": "^3.14.0",
|
"nuxt": "^3.14.159",
|
||||||
"nuxt-headlessui": "^1.2.0",
|
"nuxt-headlessui": "^1.2.0",
|
||||||
"nuxt-security": "^2.0.0",
|
"nuxt-security": "^2.1.1",
|
||||||
"nuxt-shiki": "^0.3.0",
|
"nuxt-shiki": "^0.3.0",
|
||||||
"overlayscrollbars": "^2.10.0",
|
"overlayscrollbars": "^2.10.0",
|
||||||
"overlayscrollbars-vue": "^0.5.9",
|
"overlayscrollbars-vue": "^0.5.9",
|
||||||
"shiki": "^1.22.2",
|
"shiki": "^1.23.0",
|
||||||
"vue": "^3.5.12",
|
"vue": "^3.5.12",
|
||||||
"vue-router": "^4.4.5",
|
"vue-router": "^4.4.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,14 @@
|
||||||
<template #account>
|
<template #account>
|
||||||
<ProfileEditor />
|
<ProfileEditor />
|
||||||
</template>
|
</template>
|
||||||
|
<template #emojis>
|
||||||
|
<EmojiEditor />
|
||||||
|
</template>
|
||||||
</SettingsSidebar>
|
</SettingsSidebar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import EmojiEditor from "~/components/settings/emojis/emojis.vue";
|
||||||
import ProfileEditor from "~/components/settings/profile-editor.vue";
|
import ProfileEditor from "~/components/settings/profile-editor.vue";
|
||||||
import Renderer from "~/components/settings/renderer.vue";
|
import Renderer from "~/components/settings/renderer.vue";
|
||||||
import SettingsSidebar from "~/components/sidebars/settings-sidebar.vue";
|
import SettingsSidebar from "~/components/sidebars/settings-sidebar.vue";
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ export type CodeSetting = Setting & {
|
||||||
|
|
||||||
export enum SettingPages {
|
export enum SettingPages {
|
||||||
Account = "account",
|
Account = "account",
|
||||||
|
Emojis = "emojis",
|
||||||
Behaviour = "behaviour",
|
Behaviour = "behaviour",
|
||||||
Advanced = "advanced",
|
Advanced = "advanced",
|
||||||
Appearance = "appearance",
|
Appearance = "appearance",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue