feat: new modal

This commit is contained in:
ThatOneCalculator 2023-02-18 15:27:12 -08:00
parent de17a10bb6
commit a1a6937785
No known key found for this signature in database
GPG Key ID: 8703CACD01000000
2 changed files with 142 additions and 64 deletions

View File

@ -1,12 +1,19 @@
<template> <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"> <Transition
<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 }"> :name="transitionName"
<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div> :enter-active-class="$style['transition_' + transitionName + '_enterActive']"
<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick"> :leave-active-class="$style['transition_' + transitionName + '_leaveActive']"
<slot :max-height="maxHeight" :type="type"></slot> :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>
</div> </Transition>
</transition>
</template> </template>
<script lang="ts" setup> <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<{ const props = withDefaults(defineProps<{
manualShowing?: boolean | null; manualShowing?: boolean | null;
@ -61,9 +68,10 @@ let maxHeight = $ref<number>();
let fixed = $ref(false); let fixed = $ref(false);
let transformOrigin = $ref('center'); let transformOrigin = $ref('center');
let showing = $ref(true); let showing = $ref(true);
let content = $ref<HTMLElement>(); let content = $shallowRef<HTMLElement>();
const zIndex = os.claimZIndex(props.zPriority); const zIndex = os.claimZIndex(props.zPriority);
const type = $computed(() => { let useSendAnime = $ref(false);
const type = $computed<ModalTypes>(() => {
if (props.preferType === 'auto') { if (props.preferType === 'auto') {
if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') { if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
return 'drawer'; return 'drawer';
@ -74,19 +82,47 @@ const type = $computed(() => {
return props.preferType!; 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; 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'; if (props.src) props.src.style.pointerEvents = 'auto';
showing = false; showing = false;
emit('close'); emit('close');
}; }
const onBgClick = () => { function onBgClick() {
if (contentClicking) return; if (contentClicking) return;
emit('click'); emit('click');
}; }
if (type === 'drawer') { if (type === 'drawer') {
maxHeight = window.innerHeight / 1.5; maxHeight = window.innerHeight / 1.5;
@ -230,12 +266,13 @@ const onOpened = () => {
onMounted(() => { onMounted(() => {
watch(() => props.src, async () => { watch(() => props.src, async () => {
if (props.src) { if (props.src) {
// eslint-disable-next-line vue/no-mutating-props
props.src.style.pointerEvents = 'none'; props.src.style.pointerEvents = 'none';
} }
fixed = (type === 'drawer') || (getFixedContainer(props.src) != null); fixed = (type === 'drawer') || (getFixedContainer(props.src) != null);
await nextTick(); await nextTick();
align(); align();
}, { immediate: true }); }, { immediate: true });
@ -251,8 +288,33 @@ defineExpose({
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.modal-enter-active, .modal-leave-active { .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 { > .bg {
transition: opacity 0.2s !important; transition: opacity 0.2s !important;
} }
@ -262,7 +324,8 @@ defineExpose({
transition: opacity 0.2s, transform 0.2s !important; transition: opacity 0.2s, transform 0.2s !important;
} }
} }
.modal-enter-from, .modal-leave-to { .transition_modal_enterFrom,
.transition_modal_leaveTo {
> .bg { > .bg {
opacity: 0; opacity: 0;
} }
@ -275,17 +338,19 @@ defineExpose({
} }
} }
.modal-popup-enter-active, .modal-popup-leave-active { .transition_modal-popup_enterActive,
.transition_modal-popup_leaveActive {
> .bg { > .bg {
transition: opacity 0.2s !important; transition: opacity 0.1s !important;
} }
> .content { > .content {
transform-origin: var(--transformOrigin); 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 { > .bg {
opacity: 0; opacity: 0;
} }
@ -298,7 +363,7 @@ defineExpose({
} }
} }
.modal-drawer-enter-active { .transition_modal-drawer_enterActive {
> .bg { > .bg {
transition: opacity 0.2s !important; transition: opacity 0.2s !important;
} }
@ -307,7 +372,7 @@ defineExpose({
transition: transform 0.2s cubic-bezier(0,.5,0,1) !important; transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
} }
} }
.modal-drawer-leave-active { .transition_modal-drawer_leaveActive {
> .bg { > .bg {
transition: opacity 0.2s !important; transition: opacity 0.2s !important;
} }
@ -316,7 +381,8 @@ defineExpose({
transition: transform 0.2s cubic-bezier(0,.5,0,1) !important; 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 { > .bg {
opacity: 0; opacity: 0;
} }
@ -327,15 +393,7 @@ defineExpose({
} }
} }
.qzhlnise { .root {
> .bg {
&.transparent {
background: transparent;
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
}
&.dialog { &.dialog {
> .content { > .content {
position: fixed; 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%); -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%); 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; left: 0;
right: 0; right: 0;
margin: auto; margin: auto;
> ::v-deep(*) {
margin: auto;
}
} }
} }
}
.bg {
&.bgTransparent {
background: transparent;
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
} }
</style> </style>

View File

@ -1,19 +1,46 @@
<template> <template>
<MkModal ref="modal" :prefer-type="'dialog:top'" @click="$refs.modal.close()" @closed="$emit('closed')"> <MkModal ref="modal" :prefer-type="'dialog'" @click="modal.close()" @closed="onModalClosed()">
<MkPostForm v-bind="$attrs" @posted="$refs.modal.close()" @cancel="$refs.modal.close()" @esc="$refs.modal.close()"/> <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> </MkModal>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { } from 'vue';
import MkModal from '@/components/MkModal.vue'; import * as misskey from 'calckey-js';
import MkPostForm from '@/components/MkPostForm.vue'; import MkModal from '@/components/MkModal.vue';
import MkPostForm from '@/components/MkPostForm.vue';
export default defineComponent({ const props = defineProps<{
components: { reply?: misskey.entities.Note;
MkModal, renote?: misskey.entities.Note;
MkPostForm, channel?: any; // TODO
}, mention?: misskey.entities.User;
emits: ['closed'], specified?: misskey.entities.User;
}); initialText?: string;
</script> 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>