feat: new modal
This commit is contained in:
parent
de17a10bb6
commit
a1a6937785
2 changed files with 142 additions and 64 deletions
|
@ -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']"
|
||||||
|
: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>
|
<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,6 +266,7 @@ 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);
|
||||||
|
@ -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(*) {
|
.bg {
|
||||||
margin: auto;
|
&.bgTransparent {
|
||||||
|
background: transparent;
|
||||||
|
-webkit-backdrop-filter: none;
|
||||||
|
backdrop-filter: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue