Files
stream.ui/src/components/ui/AppDialog.vue
Mr.Dat 15b69773f0 feat: enhance icons and components for improved UI
- Updated `GlobalUploadIndicator.vue` to include `watch` for better state management.
- Modified `CoinsIcon.vue`, `Globe.vue`, and `VideoPlayIcon.vue` to support filled and outlined states.
- Added new icons: `hard-drive.vue` and `shield-user.vue`.
- Improved `AppDialog.vue` to include `ClientOnly` for hydration mismatch handling.
- Refactored `BaseTable.vue` to include `ref` for better reactivity.
- Changed route redirection in `index.ts` for better clarity.
- Enhanced `Billing.vue` and `BillingTopupDialog.vue` with new icons and improved UI elements.
- Updated `PaymentHistory.tsx` and `PlanSelection.tsx` to use new icon components.
- Refined `Settings.vue` to utilize new icons and improve layout.
- Adjusted `Upload.vue` and `Videos.vue` for better component organization and imports.
- Cleaned up `auth.ts` store to include `computed` for better state management.
- Updated `tsconfig.json` to streamline TypeScript configuration.
- Removed unnecessary console log in `vite-plugin-ssr-middleware.ts`.
2026-03-25 15:17:45 +07:00

118 lines
3.4 KiB
Vue

<script setup lang="ts">
import XIcon from '@/components/icons/XIcon.vue';
import { cn } from '@/lib/utils';
import { useTranslation } from 'i18next-vue';
import { onBeforeUnmount, watch } from 'vue';
import ClientOnly from '../ClientOnly';
// Ensure client-side only rendering to avoid hydration mismatch
const props = withDefaults(defineProps<{
visible: boolean;
title?: string;
closable?: boolean;
maxWidthClass?: string;
}>(), {
title: '',
closable: true,
maxWidthClass: 'max-w-lg',
});
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'close'): void;
}>();
const { t } = useTranslation();
const close = () => {
emit('update:visible', false);
emit('close');
};
const onKeydown = (e: KeyboardEvent) => {
if (!props.visible) return;
if (!props.closable) return;
if (e.key === 'Escape') close();
};
watch(
() => props.visible,
(v) => {
if (typeof window === 'undefined') return;
if (v) {
document.body.style.overflow = 'hidden';
window.addEventListener('keydown', onKeydown);
}
else {
document.body.style.overflow = 'unset';
window.removeEventListener('keydown', onKeydown);
}
},
{ immediate: true }
);
onBeforeUnmount(() => {
if (typeof window === 'undefined') return;
window.removeEventListener('keydown', onKeydown);
});
</script>
<template>
<ClientOnly>
<Teleport to="body">
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition-all duration-150 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div v-if="visible" class="fixed inset-0 z-[9999]">
<!-- Backdrop -->
<div
class="absolute inset-0 bg-black/40"
@click="closable && close()"
aria-hidden="true"
/>
<!-- Panel -->
<div class="absolute inset-0 flex items-center justify-center p-4">
<div :class="cn('w-full bg-white border border-border rounded-lg shadow-lg overflow-hidden', maxWidthClass)">
<!-- Header slot -->
<div v-if="$slots.header" class="px-5 py-4 border-b border-border">
<slot name="header" :close="close" />
</div>
<!-- Default title -->
<div v-else-if="title" class="flex items-center justify-between gap-3 px-5 py-4 border-b border-border">
<h3 class="font-semibold text-foreground">
{{ title }}
</h3>
<button
v-if="closable"
type="button"
class="p-1 rounded-md text-foreground/60 hover:text-foreground hover:bg-muted/50 transition-all"
@click="close"
:aria-label="t('common.close')"
>
<XIcon class="w-4 h-4" />
</button>
</div>
<!-- Content -->
<div class="p-5 max-h-[80vh] overflow-y-auto">
<slot />
</div>
<!-- Footer slot -->
<div v-if="$slots.footer" class="px-5 py-4 border-t border-border bg-muted/20">
<slot name="footer" />
</div>
</div>
</div>
</div>
</Transition>
</Teleport>
</ClientOnly>
</template>