[FEAT] Streamline UI
This commit is contained in:
@@ -85,5 +85,3 @@
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -33,32 +33,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Copy to Frame Modal -->
|
||||
<div v-if="showCopyToFrameModal" class="fixed inset-0 bg-black/50 z-[60] flex items-center justify-center" @click.self="closeCopyToFrameModal">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl p-6 min-w-[320px] max-w-md" @click.stop>
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100 mb-4">Copy sprite to frame</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target frame</label>
|
||||
<select v-model.number="copyTargetFrame" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
|
||||
<option v-for="i in maxFrameCount" :key="i" :value="i - 1">Frame {{ i }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target layer</label>
|
||||
<select v-model="copyTargetLayerId" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
|
||||
<option v-for="layer in props.layers" :key="layer.id" :value="layer.id">{{ layer.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 mt-6">
|
||||
<button @click="closeCopyToFrameModal" class="px-4 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors">Cancel</button>
|
||||
<button @click="confirmCopyToFrame" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CopyToFrameModal :is-open="showCopyToFrameModal" :layers="props.layers" :initial-layer-id="copyTargetLayerId" @close="closeCopyToFrameModal" @copy="confirmCopyToFrame" />
|
||||
</Teleport>
|
||||
|
||||
<div class="h-full w-full flex flex-col p-4">
|
||||
@@ -206,6 +181,7 @@
|
||||
import { useFileDrop } from '@/composables/useFileDrop';
|
||||
import { useGridMetrics } from '@/composables/useGridMetrics';
|
||||
import { useBackgroundStyles } from '@/composables/useBackgroundStyles';
|
||||
import CopyToFrameModal from './utilities/CopyToFrameModal.vue';
|
||||
|
||||
import type { Layer } from '@/types/sprites';
|
||||
|
||||
@@ -302,15 +278,9 @@
|
||||
|
||||
// Copy to frame modal state
|
||||
const showCopyToFrameModal = ref(false);
|
||||
const copyTargetFrame = ref(0);
|
||||
const copyTargetLayerId = ref(props.activeLayerId);
|
||||
const copySpriteId = ref<string | null>(null);
|
||||
|
||||
const maxFrameCount = computed(() => {
|
||||
const maxLen = Math.max(1, ...props.layers.map(l => l.sprites.length));
|
||||
return maxLen + 1; // Allow copying to one frame beyond current max
|
||||
});
|
||||
|
||||
// Clear selection when toggling multi-select mode
|
||||
watch(
|
||||
() => props.isMultiSelectMode,
|
||||
@@ -556,7 +526,6 @@
|
||||
if (contextMenuSpriteId.value) {
|
||||
copySpriteId.value = contextMenuSpriteId.value;
|
||||
copyTargetLayerId.value = props.activeLayerId;
|
||||
copyTargetFrame.value = 0;
|
||||
showCopyToFrameModal.value = true;
|
||||
showContextMenu.value = false;
|
||||
}
|
||||
@@ -567,9 +536,9 @@
|
||||
copySpriteId.value = null;
|
||||
};
|
||||
|
||||
const confirmCopyToFrame = () => {
|
||||
const confirmCopyToFrame = (targetLayerId: string, targetFrameIndex: number) => {
|
||||
if (copySpriteId.value) {
|
||||
emit('copySpriteToFrame', copySpriteId.value, copyTargetLayerId.value, copyTargetFrame.value);
|
||||
emit('copySpriteToFrame', copySpriteId.value, targetLayerId, targetFrameIndex);
|
||||
closeCopyToFrameModal();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,32 +24,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Copy to Frame Modal -->
|
||||
<div v-if="showCopyToFrameModal" class="fixed inset-0 bg-black/50 z-[60] flex items-center justify-center" @click.self="closeCopyToFrameModal">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl p-6 min-w-[320px] max-w-md" @click.stop>
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100 mb-4">Copy Sprite to Frame</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target Frame</label>
|
||||
<select v-model.number="copyTargetFrame" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
|
||||
<option v-for="i in maxFrameCount" :key="i" :value="i - 1">Frame {{ i }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target Layer</label>
|
||||
<select v-model="copyTargetLayerId" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
|
||||
<option v-for="layer in props.layers" :key="layer.id" :value="layer.id">{{ layer.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 mt-6">
|
||||
<button @click="closeCopyToFrameModal" class="px-4 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors">Cancel</button>
|
||||
<button @click="confirmCopyToFrame" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CopyToFrameModal :is-open="showCopyToFrameModal" :layers="props.layers" :initial-layer-id="copyTargetLayerId" @close="closeCopyToFrameModal" @copy="confirmCopyToFrame" />
|
||||
</Teleport>
|
||||
|
||||
<div class="spritesheet-preview w-full h-full" @click="hideContextMenu">
|
||||
@@ -308,6 +283,7 @@
|
||||
import { useAnimationFrames } from '@/composables/useAnimationFrames';
|
||||
import { useGridMetrics } from '@/composables/useGridMetrics';
|
||||
import { useBackgroundStyles } from '@/composables/useBackgroundStyles';
|
||||
import CopyToFrameModal from './utilities/CopyToFrameModal.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
layers: Layer[];
|
||||
@@ -373,14 +349,8 @@
|
||||
|
||||
// Copy to frame modal state
|
||||
const showCopyToFrameModal = ref(false);
|
||||
const copyTargetFrame = ref(0);
|
||||
const copyTargetLayerId = ref(props.activeLayerId);
|
||||
|
||||
const maxFrameCount = computed(() => {
|
||||
const maxLen = Math.max(1, ...props.layers.map(l => l.sprites.length));
|
||||
return maxLen + 1; // Allow copying to one frame beyond current max
|
||||
});
|
||||
|
||||
// Drag and drop for new sprites
|
||||
const onDragOver = () => {
|
||||
isDragOver.value = true;
|
||||
@@ -713,7 +683,6 @@
|
||||
const openCopyToFrameModal = () => {
|
||||
if (contextMenuSpriteId.value) {
|
||||
copyTargetLayerId.value = contextMenuLayerId.value || props.activeLayerId;
|
||||
copyTargetFrame.value = 0;
|
||||
showCopyToFrameModal.value = true;
|
||||
showContextMenu.value = false;
|
||||
}
|
||||
@@ -723,9 +692,9 @@
|
||||
showCopyToFrameModal.value = false;
|
||||
};
|
||||
|
||||
const confirmCopyToFrame = () => {
|
||||
const confirmCopyToFrame = (targetLayerId: string, targetFrameIndex: number) => {
|
||||
if (contextMenuSpriteId.value) {
|
||||
emit('copySpriteToFrame', contextMenuSpriteId.value, copyTargetLayerId.value, copyTargetFrame.value);
|
||||
emit('copySpriteToFrame', contextMenuSpriteId.value, targetLayerId, targetFrameIndex);
|
||||
closeCopyToFrameModal();
|
||||
contextMenuSpriteId.value = null;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" @click.self="close">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl w-full max-w-md p-6 relative border border-gray-200 dark:border-gray-700">
|
||||
<button @click="close" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
|
||||
<Modal :is-open="isOpen" @close="close" :title="isLogin ? 'Welcome Back' : 'Create Account'" :initialWidth="450">
|
||||
<div class="space-y-4">
|
||||
<div class="text-center mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ isLogin ? 'Welcome Back' : 'Create Account' }}</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ isLogin ? 'Sign in to access your projects' : 'Join to save and manage your spritesheets' }}
|
||||
</p>
|
||||
@@ -46,13 +40,13 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useAuthStore } from '@/stores/useAuthStore';
|
||||
import Modal from '@/components/utilities/Modal.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" @click.self="close">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-xl p-6 w-full max-w-sm border border-gray-200 dark:border-gray-700">
|
||||
<h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">New project</h2>
|
||||
<Modal :is-open="isOpen" @close="close" title="New project" :initialWidth="400">
|
||||
<div class="px-2 pt-2 pb-1">
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
@@ -38,12 +36,12 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import Modal from '@/components/utilities/Modal.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4" @click.self="close">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl overflow-hidden border border-gray-200 dark:border-gray-800 flex flex-col h-[85vh] animate-fade-in-up">
|
||||
<!-- Header -->
|
||||
<div class="px-6 py-5 border-b border-gray-100 dark:border-gray-800 flex flex-col gap-4 bg-gray-50/50 dark:bg-gray-800/20">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-gray-100">My projects</h2>
|
||||
<Modal :is-open="isOpen" @close="close" title="My Projects" :initialWidth="900" :initialHeight="800" noPadding>
|
||||
<div class="flex flex-col h-full bg-white dark:bg-gray-900">
|
||||
<!-- Search Header -->
|
||||
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-800/20">
|
||||
<div class="mb-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Manage your saved sprite sheets</p>
|
||||
</div>
|
||||
<button @click="close" class="w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Search Bar -->
|
||||
<div class="relative">
|
||||
<i class="fas fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
|
||||
<input
|
||||
@@ -121,14 +113,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, toRefs, watch, ref, computed } from 'vue';
|
||||
import { useProjectStore, type Project } from '@/stores/useProjectStore';
|
||||
import { useProjectManager } from '@/composables/useProjectManager';
|
||||
import Modal from '@/components/utilities/Modal.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" @click.self="close">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-xl p-6 w-full max-w-sm border border-gray-200 dark:border-gray-700">
|
||||
<h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">Save project</h2>
|
||||
<Modal :is-open="isOpen" @close="close" title="Save project" :initialWidth="400">
|
||||
<div class="px-2 pt-2 pb-1">
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Project Name</label>
|
||||
@@ -14,12 +12,12 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import Modal from '@/components/utilities/Modal.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
isOpen: boolean;
|
||||
|
||||
72
src/components/utilities/CopyToFrameModal.vue
Normal file
72
src/components/utilities/CopyToFrameModal.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<Modal :is-open="isOpen" @close="close" title="Copy sprite to frame" :initialWidth="380" :initialHeight="320">
|
||||
<div class="p-4 flex flex-col h-full">
|
||||
<div class="space-y-4 flex-1">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target frame</label>
|
||||
<select v-model.number="targetFrame" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
|
||||
<option v-for="i in maxFrameCount" :key="i" :value="i - 1">Frame {{ i }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target layer</label>
|
||||
<select v-model="targetLayerId" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
|
||||
<option v-for="layer in layers" :key="layer.id" :value="layer.id">{{ layer.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 mt-6 pt-4 border-t border-gray-100 dark:border-gray-700">
|
||||
<button @click="close" class="px-4 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors font-medium text-sm">Cancel</button>
|
||||
<button @click="confirm" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium text-sm">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import Modal from './Modal.vue';
|
||||
import type { Layer } from '@/types/sprites';
|
||||
|
||||
const props = defineProps<{
|
||||
isOpen: boolean;
|
||||
layers: Layer[];
|
||||
initialLayerId?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'copy', targetLayerId: string, targetFrameIndex: number): void;
|
||||
}>();
|
||||
|
||||
const targetFrame = ref(0);
|
||||
const targetLayerId = ref('');
|
||||
|
||||
const maxFrameCount = computed(() => {
|
||||
const maxLen = Math.max(1, ...props.layers.map(l => l.sprites.length));
|
||||
return maxLen + 1; // Allow copying to one frame beyond current max
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.isOpen,
|
||||
isOpen => {
|
||||
if (isOpen) {
|
||||
targetFrame.value = 0;
|
||||
targetLayerId.value = props.initialLayerId || (props.layers.length > 0 ? props.layers[0].id : '');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const confirm = () => {
|
||||
if (targetLayerId.value) {
|
||||
emit('copy', targetLayerId.value, targetFrame.value);
|
||||
close();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -10,31 +10,39 @@
|
||||
width: isFullScreen ? '100%' : `${size.width}px`,
|
||||
height: isFullScreen ? '100%' : `${size.height}px`,
|
||||
}"
|
||||
class="bg-white dark:bg-gray-800 rounded-2xl border-2 border-gray-300 dark:border-gray-700 shadow-xl flex flex-col fixed z-50 transition-colors duration-300"
|
||||
class="bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 shadow-2xl flex flex-col fixed z-50 transition-colors duration-300"
|
||||
:class="{ 'rounded-none border-0': isFullScreen, 'select-none': isDragging }"
|
||||
>
|
||||
<!-- Header with drag handle -->
|
||||
<div class="flex justify-between items-center p-4 border-b border-gray-200 dark:border-gray-700" :class="{ 'cursor-move': !isFullScreen && !isMobile }" @mousedown="startDrag" @touchstart="handleTouchStart">
|
||||
<h3 class="text-xl sm:text-2xl font-semibold text-gray-900 dark:text-gray-100 truncate pr-2">{{ title }}</h3>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button @click="toggleFullScreen" @mousedown.stop @touchstart.stop class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" data-rybbit-event="modal-fullscreen">
|
||||
<img src="@/assets/images/fullscreen-icon.svg" class="w-4 h-4 dark:invert" alt="Fullscreen" :class="{ 'rotate-180': isFullScreen }" />
|
||||
<div class="flex justify-between items-center px-5 py-4 border-b border-gray-100 dark:border-gray-700/50" :class="{ 'cursor-move': !isFullScreen && !isMobile }" @mousedown="startDrag" @touchstart="handleTouchStart">
|
||||
<h3 class="text-lg sm:text-lg font-bold text-gray-800 dark:text-gray-100 truncate pr-2">{{ title }}</h3>
|
||||
<div class="flex items-center space-x-1">
|
||||
<button @click="toggleFullScreen" @mousedown.stop @touchstart.stop class="p-1.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-all" data-rybbit-event="modal-fullscreen">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-if="!isFullScreen">
|
||||
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-else>
|
||||
<path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3" />
|
||||
</svg>
|
||||
</button>
|
||||
<button @click="close" @mousedown.stop @touchstart.stop class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" data-rybbit-event="modal-close">
|
||||
<img src="@/assets/images/close-icon.svg" class="w-5 h-5 dark:invert" alt="Close" />
|
||||
<button @click="close" @mousedown.stop @touchstart.stop class="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md transition-all" data-rybbit-event="modal-close">
|
||||
<svg 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">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="p-4 sm:p-6 flex-1 overflow-auto">
|
||||
<div :class="[noPadding ? '' : 'p-5 sm:p-6', 'flex-1 overflow-auto']">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<!-- Resize handle -->
|
||||
<div v-if="!isFullScreen && !isMobile" class="absolute bottom-0 right-0 w-8 h-8 cursor-se-resize" @mousedown="startResize" @touchstart="startResize">
|
||||
<svg class="w-8 h-8 text-gray-400 dark:text-gray-500" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M22 22H20V20H22V22ZM22 18H20V16H22V18ZM18 22H16V20H18V22ZM22 14H20V12H22V14ZM18 18H16V16H18V18ZM14 22H12V20H14V22Z" />
|
||||
<div v-if="!isFullScreen && !isMobile" class="absolute bottom-0 right-0 w-6 h-6 cursor-se-resize flex items-end justify-end p-1 opacity-50 hover:opacity-100 transition-opacity" @mousedown="startResize" @touchstart="startResize">
|
||||
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M22 22H20V20H22V22ZM22 18H20V16H22V18ZM18 22H16V20H18V22Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,6 +57,7 @@
|
||||
title: string;
|
||||
initialWidth?: number;
|
||||
initialHeight?: number;
|
||||
noPadding?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
Reference in New Issue
Block a user