fix color

This commit is contained in:
2026-01-26 16:26:58 +07:00
parent 820aa7a597
commit 6c4015f8c4
29 changed files with 867 additions and 306 deletions

View File

@@ -6,6 +6,7 @@ import Video from "@/components/icons/Video.vue";
import Credit from "@/components/icons/Credit.vue";
import Upload from "./icons/Upload.vue";
import NotificationDrawer from "./NotificationDrawer.vue";
import GlobalUploadIndicator from "./GlobalUploadIndicator.vue";
import { cn } from "@/lib/utils";
import { createStaticVNode, ref } from "vue";
@@ -38,14 +39,9 @@ const links = [
<header
class=":uno: fixed left-0 w-18 flex flex-col items-center pt-4 gap-6 z-41 max-h-screen h-screen border-r border-gray-200 bg-white">
<template v-for="i in links" :key="i.label">
<component
:name="i.label"
:is="i.type === 'a' ? 'router-link' : 'div'"
v-bind="i.type === 'a' ? { to: i.href } : {}"
v-tooltip="i.label"
@click="i.action && i.action($event)"
:class="cn(i.className, ($route.path === i.href || i.isActive?.value) && 'bg-primary/15')"
>
<component :name="i.label" :is="i.type === 'a' ? 'router-link' : 'div'"
v-bind="i.type === 'a' ? { to: i.href } : {}" v-tooltip="i.label" @click="i.action && i.action($event)"
:class="cn(i.className, ($route.path === i.href || i.isActive?.value) && 'bg-primary/15')">
<component :is="i.icon" class="w-6 h-6" :filled="$route.path === i.href || i.isActive?.value" />
</component>
</template>
@@ -53,11 +49,11 @@ const links = [
<NotificationDrawer ref="notificationPopover" @change="(val) => isNotificationOpen = val" />
</ClientOnly>
</header>
<main class="flex flex-1 overflow-hidden md:ps-18">
<div class="flex-1 overflow-auto p-4 bg-white rounded-lg md:(mr-2 mb-2) min-h-[calc(100vh-8rem)]">
<div class=":uno: flex-1 overflow-auto p-4 bg-[#FAF8F8] rounded-lg md:(mr-2 mb-2) min-h-[calc(100vh-8rem)]">
<router-view v-slot="{ Component }">
<Transition enter-active-class="transition-all duration-300 ease-in-out"
enter-from-class="opacity-0 transform translate-y-4"
@@ -69,6 +65,6 @@ const links = [
</Transition>
</router-view>
</div>
<GlobalUploadIndicator />
</main>
</template>

View File

@@ -0,0 +1,103 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useUploadQueue } from '@/composables/useUploadQueue';
import { useRoute, useRouter } from 'vue-router';
import UploadQueueItem from '@/routes/upload/components/UploadQueueItem.vue';
const { items, totalSize, completeCount, pendingCount } = useUploadQueue();
const route = useRoute();
const router = useRouter();
const isOpen = ref(false);
const isVisible = computed(() => {
// Show if there are items AND we are NOT on the upload page
return items.value.length > 0 && route.path !== '/upload';
});
const progress = computed(() => {
if (items.value.length === 0) return 0;
const totalProgress = items.value.reduce((acc, item) => acc + (item.progress || 0), 0);
return Math.round(totalProgress / items.value.length);
});
const isUploading = computed(() => {
return items.value.some(i => i.status === 'uploading' || i.status === 'fetching');
});
const toggleOpen = () => {
isOpen.value = !isOpen.value;
};
const goToUploadPage = () => {
router.push('/upload');
isOpen.value = false;
};
</script>
<template>
<div v-if="isVisible" class="fixed bottom-6 right-6 z-50 flex flex-col items-end gap-2">
<!-- Mini Queue Popover -->
<Transition enter-active-class="transition duration-200 ease-out"
enter-from-class="opacity-0 translate-y-2 scale-95" enter-to-class="opacity-100 translate-y-0 scale-100"
leave-active-class="transition duration-150 ease-in" leave-from-class="opacity-100 translate-y-0 scale-100"
leave-to-class="opacity-0 translate-y-2 scale-95">
<div v-if="isOpen"
class="bg-white rounded-2xl shadow-xl border border-gray-100 p-4 mb-2 w-80 max-h-[60vh] flex flex-col">
<div class="flex items-center justify-between mb-3 pb-3 border-b border-gray-100">
<h3 class="font-bold text-slate-800">Uploads</h3>
<button @click="goToUploadPage" class="text-xs font-bold text-accent hover:underline">
View All
</button>
</div>
<div
class="flex-1 overflow-y-auto min-h-0 space-y-3 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-slate-300 [&::-webkit-scrollbar-thumb]:rounded">
<UploadQueueItem v-for="item in items" :key="item.id" :item="item" :minimal="true"
class="border-b border-slate-100 last:border-0 !rounded-none" />
</div>
</div>
</Transition>
<!-- Floating Button -->
<button @click="toggleOpen"
class="relative flex items-center gap-3 bg-white pl-4 pr-5 py-3 rounded-full shadow-[0_8px_30px_rgba(0,0,0,0.12)] border border-slate-100 hover:-translate-y-1 transition-all duration-300 group">
<!-- Progress Ring -->
<div class="relative w-10 h-10 flex items-center justify-center">
<svg class="w-full h-full -rotate-90 text-slate-100" viewBox="0 0 36 36">
<path class="stroke-current" fill="none" stroke-width="3"
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
</svg>
<svg class="absolute inset-0 w-full h-full -rotate-90 text-accent transition-all duration-500"
viewBox="0 0 36 36" :style="{ strokeDasharray: `${progress}, 100` }">
<path class="stroke-current" fill="none" stroke-width="3" stroke-linecap="round"
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
</svg>
<div class="absolute inset-0 flex items-center justify-center text-accent">
<svg v-if="!isUploading && completeCount === items.length" xmlns="http://www.w3.org/2000/svg"
class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M20 6 9 17l-5-5" />
</svg>
<span v-else class="text-[10px] font-bold">{{ progress }}%</span>
</div>
</div>
<div class="text-left">
<div class="text-sm font-bold text-slate-800 group-hover:text-accent transition-colors">
{{ isUploading ? 'Uploading...' : (completeCount === items.length ? 'Completed' : 'Pending') }}
</div>
<div class="text-xs text-slate-500">
{{ completeCount }} / {{ items.length }} files
</div>
</div>
<div v-if="pendingCount"
class="absolute -top-1 -right-1 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center text-[10px] font-bold text-white shadow-sm border-2 border-white">
{{ pendingCount }}
</div>
</button>
</div>
</template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import NotificationItem from '@/routes/notification/components/NotificationItem.vue';
import { onClickOutside } from '@vueuse/core';
import { computed, ref, watch } from 'vue';
// Emit event when visibility changes
const emit = defineEmits(['change']);
@@ -97,7 +97,7 @@ onClickOutside(drawerRef, (event) => {
visible.value = false;
}
}, {
ignore: ['.press-animated', '[name="Notification"]'] // Assuming the trigger button has this class or we can suggest adding a class to the trigger
ignore: ['[name="Notification"]'] // Assuming the trigger button has this class or we can suggest adding a class to the trigger
});
const handleMarkRead = (id: string) => {
@@ -122,35 +122,23 @@ defineExpose({ toggle });
<template>
<Teleport to="body">
<Transition
enter-active-class="transition-all duration-300 ease-out"
enter-from-class="opacity-0 -translate-x-4"
enter-to-class="opacity-100 translate-x-0"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 translate-x-0"
leave-to-class="opacity-0 -translate-x-4"
>
<div
v-if="visible"
ref="drawerRef"
class="fixed top-0 left-[80px] bottom-0 w-[380px] bg-white rounded-2xl border border-gray-300 p-3 z-50 flex flex-col shadow-lg my-3"
>
<Transition enter-active-class="transition-all duration-300 ease-out"
enter-from-class="opacity-0 -translate-x-4" enter-to-class="opacity-100 translate-x-0"
leave-active-class="transition-all duration-200 ease-in" leave-from-class="opacity-100 translate-x-0"
leave-to-class="opacity-0 -translate-x-4">
<div v-if="visible" ref="drawerRef"
class="fixed top-0 left-[80px] bottom-0 w-[380px] bg-white rounded-2xl border border-gray-300 p-3 z-50 flex flex-col shadow-lg my-3">
<!-- Header -->
<div class="flex items-center justify-between p-4">
<div class="flex items-center gap-2">
<h3 class="font-semibold text-gray-900">Notifications</h3>
<span
v-if="unreadCount > 0"
class="px-2 py-0.5 text-xs font-medium bg-primary text-white rounded-full"
>
<span v-if="unreadCount > 0"
class="px-2 py-0.5 text-xs font-medium bg-primary text-white rounded-full">
{{ unreadCount }}
</span>
</div>
<button
v-if="unreadCount > 0"
@click="handleMarkAllRead"
class="text-sm text-primary hover:underline font-medium"
>
<button v-if="unreadCount > 0" @click="handleMarkAllRead"
class="text-sm text-primary hover:underline font-medium">
Mark all read
</button>
</div>
@@ -158,16 +146,10 @@ defineExpose({ toggle });
<!-- Notification List -->
<div class="flex flex-col flex-1 overflow-y-auto gap-2">
<template v-if="notifications.length > 0">
<div
v-for="notification in notifications"
:key="notification.id"
class="border-b border-gray-50 last:border-0"
>
<NotificationItem
:notification="notification"
@mark-read="handleMarkRead"
@delete="handleDelete"
/>
<div v-for="notification in notifications" :key="notification.id"
class="border-b border-gray-50 last:border-0">
<NotificationItem :notification="notification" @mark-read="handleMarkRead"
@delete="handleDelete" isDrawer />
</div>
</template>
@@ -180,11 +162,9 @@ defineExpose({ toggle });
<!-- Footer -->
<div v-if="notifications.length > 0" class="p-3 border-t border-gray-100 bg-gray-50/50">
<router-link
to="/notification"
<router-link to="/notification"
class="block w-full text-center text-sm text-primary font-medium hover:underline"
@click="visible = false"
>
@click="visible = false">
View all notifications
</router-link>
</div>

View File

@@ -14,17 +14,17 @@ interface Props {
color?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
}
const props = withDefaults(defineProps<Props>(), {
withDefaults(defineProps<Props>(), {
color: 'primary'
});
const gradients = {
primary: 'from-primary/20 to-primary/5',
success: 'from-success/20 to-success/5',
warning: 'from-yellow-100 to-yellow-50',
danger: 'from-danger/20 to-danger/5',
info: 'from-info/20 to-info/5',
};
// const gradients = {
// primary: 'from-primary/20 to-primary/5',
// success: 'from-success/20 to-success/5',
// warning: 'from-yellow-100 to-yellow-50',
// danger: 'from-danger/20 to-danger/5',
// info: 'from-info/20 to-info/5',
// };
const iconColors = {
primary: 'text-primary',
@@ -37,10 +37,10 @@ const iconColors = {
<template>
<div :class="[
'stats-card relative overflow-hidden rounded-2xl p-6 bg-gradient-to-br',
gradients[color],
'border border-white/50 shadow-sm hover:shadow-md transition-all duration-300',
'group cursor-pointer'
'transform translate-y-0 relative overflow-hidden rounded-2xl p-6 bg-white',
// gradients[color],
'border border-gray-300 transition-all duration-300',
// 'group cursor-pointer'
]">
<!-- Content -->
<div class="relative z-10">
@@ -65,10 +65,15 @@ const iconColors = {
'flex items-center gap-1 font-medium',
trend.isPositive ? 'text-success' : 'text-danger'
]">
<span :class="[
<!-- <span :class="[
'w-4 h-4',
trend.isPositive ? 'i-heroicons-arrow-trending-up' : 'i-heroicons-arrow-trending-down'
]" />
]" /> -->
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path v-if="trend.isPositive" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 17l6-6 4 4 8-8" />
<path v-else stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 7l-6 6-4-4-8 8" />
</svg>
{{ Math.abs(trend.value) }}%
</span>
<span class="text-gray-500">vs last month</span>
@@ -76,13 +81,3 @@ const iconColors = {
</div>
</div>
</template>
<style scoped>
.stats-card {
transform: translateY(0);
}
.stats-card:hover {
transform: translateY(-2px);
}
</style>

View File

@@ -0,0 +1,16 @@
<template>
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
stroke="none">
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
<line x1="12" y1="9" x2="12" y2="13"></line>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>
</template>
<script lang="ts" setup>
defineProps<{ filled?: boolean }>();
</script>

View File

@@ -0,0 +1,15 @@
<template>
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
stroke="none">
<path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
</svg>
</template>
<script lang="ts" setup>
defineProps<{ filled?: boolean }>();
</script>

View File

@@ -0,0 +1,16 @@
<template>
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
stroke="none">
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
</template>
<script lang="ts" setup>
defineProps<{ filled?: boolean }>();
</script>

View File

@@ -0,0 +1,14 @@
<template>
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
stroke="none">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</template>
<script lang="ts" setup>
defineProps<{ filled?: boolean }>();
</script>

View File

@@ -0,0 +1,16 @@
<template>
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
stroke="none">
<path
d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
<line x1="1" y1="10" x2="23" y2="10"></line>
</svg>
</template>
<script lang="ts" setup>
defineProps<{ filled?: boolean }>();
</script>

View File

@@ -0,0 +1,16 @@
<template>
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
stroke="none">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</template>
<script lang="ts" setup>
defineProps<{ filled?: boolean }>();
</script>

View File

@@ -0,0 +1,18 @@
<template>
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
stroke="none">
<path
d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.488.488 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.48.48 0 0 0-.59.22L5.09 8.87a.484.484 0 0 0 .12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.48.48 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.21.08.47 0 .59-.22l1.92-3.32a.48.48 0 0 0-.12-.61l-2.03-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path
d="M12.22 2h-.44a2 2 0 0 1-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.1a2 2 0 0 1-1-1.72v-.51a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z">
</path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</template>
<script lang="ts" setup>
defineProps<{ filled?: boolean }>();
</script>

View File

@@ -0,0 +1,17 @@
<template>
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
stroke="none">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</template>
<script lang="ts" setup>
defineProps<{ filled?: boolean }>();
</script>

View File

@@ -0,0 +1,16 @@
<template>
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
stroke="none">
<path
d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="23 7 16 12 23 17 23 7"></polygon>
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>
</svg>
</template>
<script lang="ts" setup>
defineProps<{ filled?: boolean }>();
</script>

View File

@@ -0,0 +1,17 @@
<template>
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
stroke="none">
<path
d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="15" y1="9" x2="9" y2="15"></line>
<line x1="9" y1="9" x2="15" y2="15"></line>
</svg>
</template>
<script lang="ts" setup>
defineProps<{ filled?: boolean }>();
</script>