feat: new modal
This commit is contained in:
parent
de17a10bb6
commit
a1a6937785
|
@ -1,12 +1,19 @@
|
|||
<template>
|
||||
<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
|
||||
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
||||
<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
||||
<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
|
||||
<slot :max-height="maxHeight" :type="type"></slot>
|
||||
<Transition
|
||||
:name="transitionName"
|
||||
:enter-active-class="$style['transition_' + transitionName + '_enterActive']"
|
||||
:leave-active-class="$style['transition_' + transitionName + '_leaveActive']"
|
||||
:enter-from-class="$style['transition_' + transitionName + '_enterFrom']"
|
||||
:leave-to-class="$style['transition_' + transitionName + '_leaveTo']"
|
||||
:duration="transitionDuration" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened"
|
||||
>
|
||||
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" :class="[$style.root, { [$style.drawer]: type === 'drawer', [$style.dialog]: type === 'dialog', [$style.popup]: type === 'popup' }]" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
||||
<div class="_modalBg data-cy-bg" :class="[$style.bg, { [$style.bgTransparent]: isEnableBgTransparent, 'data-cy-transparent': isEnableBgTransparent }]" :style="{ zIndex }" @click="onBgClick" @mousedown="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
||||
<div ref="content" :class="[$style.content, { [$style.fixed]: fixed }]" :style="{ zIndex }" @click.self="onBgClick">
|
||||
<slot :max-height="maxHeight" :type="type"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -26,7 +33,7 @@ function getFixedContainer(el: Element | null): Element | null {
|
|||
}
|
||||
}
|
||||
|
||||
type ModalTypes = 'popup' | 'dialog' | 'dialog:top' | 'drawer';
|
||||
type ModalTypes = 'popup' | 'dialog' | 'drawer';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
manualShowing?: boolean | null;
|
||||
|
@ -61,9 +68,10 @@ let maxHeight = $ref<number>();
|
|||
let fixed = $ref(false);
|
||||
let transformOrigin = $ref('center');
|
||||
let showing = $ref(true);
|
||||
let content = $ref<HTMLElement>();
|
||||
let content = $shallowRef<HTMLElement>();
|
||||
const zIndex = os.claimZIndex(props.zPriority);
|
||||
const type = $computed(() => {
|
||||
let useSendAnime = $ref(false);
|
||||
const type = $computed<ModalTypes>(() => {
|
||||
if (props.preferType === 'auto') {
|
||||
if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
|
||||
return 'drawer';
|
||||
|
@ -74,19 +82,47 @@ const type = $computed(() => {
|
|||
return props.preferType!;
|
||||
}
|
||||
});
|
||||
const isEnableBgTransparent = $computed(() => props.transparentBg && (type === 'popup'));
|
||||
let transitionName = $computed((() =>
|
||||
defaultStore.state.animation
|
||||
? useSendAnime
|
||||
? 'send'
|
||||
: type === 'drawer'
|
||||
? 'modal-drawer'
|
||||
: type === 'popup'
|
||||
? 'modal-popup'
|
||||
: 'modal'
|
||||
: ''
|
||||
));
|
||||
let transitionDuration = $computed((() =>
|
||||
transitionName === 'send'
|
||||
? 400
|
||||
: transitionName === 'modal-popup'
|
||||
? 100
|
||||
: transitionName === 'modal'
|
||||
? 200
|
||||
: transitionName === 'modal-drawer'
|
||||
? 200
|
||||
: 0
|
||||
));
|
||||
|
||||
let contentClicking = false;
|
||||
|
||||
const close = () => {
|
||||
function close(opts: { useSendAnimation?: boolean } = {}) {
|
||||
if (opts.useSendAnimation) {
|
||||
useSendAnime = true;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
if (props.src) props.src.style.pointerEvents = 'auto';
|
||||
showing = false;
|
||||
emit('close');
|
||||
};
|
||||
}
|
||||
|
||||
const onBgClick = () => {
|
||||
function onBgClick() {
|
||||
if (contentClicking) return;
|
||||
emit('click');
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'drawer') {
|
||||
maxHeight = window.innerHeight / 1.5;
|
||||
|
@ -230,6 +266,7 @@ const onOpened = () => {
|
|||
onMounted(() => {
|
||||
watch(() => props.src, async () => {
|
||||
if (props.src) {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
props.src.style.pointerEvents = 'none';
|
||||
}
|
||||
fixed = (type === 'drawer') || (getFixedContainer(props.src) != null);
|
||||
|
@ -251,8 +288,33 @@ defineExpose({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.modal-enter-active, .modal-leave-active {
|
||||
<style lang="scss" module>
|
||||
.transition_send_enterActive,
|
||||
.transition_send_leaveActive {
|
||||
> .bg {
|
||||
transition: opacity 0.3s !important;
|
||||
}
|
||||
|
||||
> .content {
|
||||
transform: translateY(0px);
|
||||
transition: opacity 0.3s ease-in, transform 0.3s cubic-bezier(.5,-0.5,1,.5) !important;
|
||||
}
|
||||
}
|
||||
.transition_send_enterFrom,
|
||||
.transition_send_leaveTo {
|
||||
> .bg {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
> .content {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transform: translateY(-300px);
|
||||
}
|
||||
}
|
||||
|
||||
.transition_modal_enterActive,
|
||||
.transition_modal_leaveActive {
|
||||
> .bg {
|
||||
transition: opacity 0.2s !important;
|
||||
}
|
||||
|
@ -262,7 +324,8 @@ defineExpose({
|
|||
transition: opacity 0.2s, transform 0.2s !important;
|
||||
}
|
||||
}
|
||||
.modal-enter-from, .modal-leave-to {
|
||||
.transition_modal_enterFrom,
|
||||
.transition_modal_leaveTo {
|
||||
> .bg {
|
||||
opacity: 0;
|
||||
}
|
||||
|
@ -275,17 +338,19 @@ defineExpose({
|
|||
}
|
||||
}
|
||||
|
||||
.modal-popup-enter-active, .modal-popup-leave-active {
|
||||
.transition_modal-popup_enterActive,
|
||||
.transition_modal-popup_leaveActive {
|
||||
> .bg {
|
||||
transition: opacity 0.2s !important;
|
||||
transition: opacity 0.1s !important;
|
||||
}
|
||||
|
||||
> .content {
|
||||
transform-origin: var(--transformOrigin);
|
||||
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important;
|
||||
transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1), transform 0.1s cubic-bezier(0, 0, 0.2, 1) !important;
|
||||
}
|
||||
}
|
||||
.modal-popup-enter-from, .modal-popup-leave-to {
|
||||
.transition_modal-popup_enterFrom,
|
||||
.transition_modal-popup_leaveTo {
|
||||
> .bg {
|
||||
opacity: 0;
|
||||
}
|
||||
|
@ -298,7 +363,7 @@ defineExpose({
|
|||
}
|
||||
}
|
||||
|
||||
.modal-drawer-enter-active {
|
||||
.transition_modal-drawer_enterActive {
|
||||
> .bg {
|
||||
transition: opacity 0.2s !important;
|
||||
}
|
||||
|
@ -307,7 +372,7 @@ defineExpose({
|
|||
transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
|
||||
}
|
||||
}
|
||||
.modal-drawer-leave-active {
|
||||
.transition_modal-drawer_leaveActive {
|
||||
> .bg {
|
||||
transition: opacity 0.2s !important;
|
||||
}
|
||||
|
@ -316,7 +381,8 @@ defineExpose({
|
|||
transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
|
||||
}
|
||||
}
|
||||
.modal-drawer-enter-from, .modal-drawer-leave-to {
|
||||
.transition_modal-drawer_enterFrom,
|
||||
.transition_modal-drawer_leaveTo {
|
||||
> .bg {
|
||||
opacity: 0;
|
||||
}
|
||||
|
@ -327,15 +393,7 @@ defineExpose({
|
|||
}
|
||||
}
|
||||
|
||||
.qzhlnise {
|
||||
> .bg {
|
||||
&.transparent {
|
||||
background: transparent;
|
||||
-webkit-backdrop-filter: none;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
.root {
|
||||
&.dialog {
|
||||
> .content {
|
||||
position: fixed;
|
||||
|
@ -356,16 +414,6 @@ defineExpose({
|
|||
-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
|
||||
mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
|
||||
}
|
||||
|
||||
> ::v-deep(*) {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
&.top {
|
||||
> ::v-deep(*) {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,12 +441,15 @@ defineExpose({
|
|||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
|
||||
> ::v-deep(*) {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bg {
|
||||
&.bgTransparent {
|
||||
background: transparent;
|
||||
-webkit-backdrop-filter: none;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,19 +1,46 @@
|
|||
<template>
|
||||
<MkModal ref="modal" :prefer-type="'dialog:top'" @click="$refs.modal.close()" @closed="$emit('closed')">
|
||||
<MkPostForm v-bind="$attrs" @posted="$refs.modal.close()" @cancel="$refs.modal.close()" @esc="$refs.modal.close()"/>
|
||||
</MkModal>
|
||||
</template>
|
||||
<MkModal ref="modal" :prefer-type="'dialog'" @click="modal.close()" @closed="onModalClosed()">
|
||||
<MkPostForm ref="form" style="margin: 0 auto auto auto;" v-bind="props" autofocus freeze-after-posted @posted="onPosted" @cancel="modal.close()" @esc="modal.close()"/>
|
||||
</MkModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import MkPostForm from '@/components/MkPostForm.vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import * as misskey from 'calckey-js';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import MkPostForm from '@/components/MkPostForm.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkModal,
|
||||
MkPostForm,
|
||||
},
|
||||
emits: ['closed'],
|
||||
});
|
||||
</script>
|
||||
const props = defineProps<{
|
||||
reply?: misskey.entities.Note;
|
||||
renote?: misskey.entities.Note;
|
||||
channel?: any; // TODO
|
||||
mention?: misskey.entities.User;
|
||||
specified?: misskey.entities.User;
|
||||
initialText?: string;
|
||||
initialVisibility?: typeof misskey.noteVisibilities;
|
||||
initialFiles?: misskey.entities.DriveFile[];
|
||||
initialLocalOnly?: boolean;
|
||||
initialVisibleUsers?: misskey.entities.User[];
|
||||
initialNote?: misskey.entities.Note;
|
||||
instant?: boolean;
|
||||
fixed?: boolean;
|
||||
autofocus?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
let modal = $shallowRef<InstanceType<typeof MkModal>>();
|
||||
let form = $shallowRef<InstanceType<typeof MkPostForm>>();
|
||||
|
||||
function onPosted() {
|
||||
modal.close({
|
||||
useSendAnimation: true,
|
||||
});
|
||||
}
|
||||
|
||||
function onModalClosed() {
|
||||
emit('closed');
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue