summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components/MkSpot.vue
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2025-11-07 11:03:33 +0900
committerGitHub <noreply@github.com>2025-11-07 11:03:33 +0900
commite312283ea016630521c972cd42439655759a48a7 (patch)
treee01190eadda5b012532ff47af42a7dd3fc6210bc /packages/frontend/src/components/MkSpot.vue
parentUpdate package.json (diff)
downloadmisskey-e312283ea016630521c972cd42439655759a48a7.tar.gz
misskey-e312283ea016630521c972cd42439655759a48a7.tar.bz2
misskey-e312283ea016630521c972cd42439655759a48a7.zip
enhance(frontend): 投稿フォームのヒントを追加 (#16712)
* wip * wip * Update MkSpot.vue * Update MkPostForm.vue * wip * wip * Update CHANGELOG.md
Diffstat (limited to 'packages/frontend/src/components/MkSpot.vue')
-rw-r--r--packages/frontend/src/components/MkSpot.vue161
1 files changed, 161 insertions, 0 deletions
diff --git a/packages/frontend/src/components/MkSpot.vue b/packages/frontend/src/components/MkSpot.vue
new file mode 100644
index 0000000000..07699644aa
--- /dev/null
+++ b/packages/frontend/src/components/MkSpot.vue
@@ -0,0 +1,161 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div ref="rootEl" :class="$style.root" :style="{ zIndex }">
+ <div :class="[$style.bg]"></div>
+ <div ref="spotEl" :class="$style.spot"></div>
+ <div ref="bodyEl" :class="$style.body" class="_panel _shadow">
+ <div class="_gaps_s">
+ <div><b>{{ title }}</b></div>
+ <div>{{ description }}</div>
+ <div class="_buttons">
+ <MkButton v-if="hasPrev" small @click="prev"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
+ <MkButton v-if="hasNext" small primary @click="next">{{ i18n.ts.next }} <i class="ti ti-arrow-right"></i></MkButton>
+ <MkButton v-else small primary @click="next">{{ i18n.ts.done }} <i class="ti ti-check"></i></MkButton>
+ </div>
+ </div>
+ </div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { nextTick, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
+import { calcPopupPosition } from '@/utility/popup-position.js';
+import * as os from '@/os.js';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n.js';
+
+const props = withDefaults(defineProps<{
+ title: string;
+ description: string;
+ anchorElement?: HTMLElement;
+ x?: number;
+ y?: number;
+ direction?: 'top' | 'bottom' | 'right' | 'left';
+ hasPrev: boolean;
+ hasNext: boolean;
+}>(), {
+ direction: 'top',
+});
+
+const emit = defineEmits<{
+ (prev: 'prev'): void;
+ (next: 'next'): void;
+}>();
+
+function prev() {
+ emit('prev');
+}
+
+function next() {
+ emit('next');
+}
+
+const rootEl = useTemplateRef('rootEl');
+const bodyEl = useTemplateRef('bodyEl');
+const spotEl = useTemplateRef('spotEl');
+const zIndex = os.claimZIndex('high');
+const spotX = ref(0);
+const spotY = ref(0);
+const spotWidth = ref(0);
+const spotHeight = ref(0);
+
+function setPosition() {
+ if (spotEl.value == null) return;
+ if (bodyEl.value == null) return;
+ if (props.anchorElement == null) return;
+
+ const rect = props.anchorElement.getBoundingClientRect();
+ spotX.value = rect.left;
+ spotY.value = rect.top;
+ spotWidth.value = rect.width;
+ spotHeight.value = rect.height;
+
+ const data = calcPopupPosition(bodyEl.value, {
+ anchorElement: props.anchorElement,
+ direction: props.direction,
+ align: 'center',
+ innerMargin: 16,
+ x: props.x,
+ y: props.y,
+ });
+
+ bodyEl.value.style.transformOrigin = data.transformOrigin;
+ bodyEl.value.style.left = data.left + 'px';
+ bodyEl.value.style.top = data.top + 'px';
+}
+
+let loopHandler;
+
+onMounted(() => {
+ nextTick(() => {
+ setPosition();
+
+ const loop = () => {
+ setPosition();
+ loopHandler = window.requestAnimationFrame(loop);
+ };
+
+ loop();
+ });
+});
+
+onUnmounted(() => {
+ window.cancelAnimationFrame(loopHandler);
+});
+</script>
+
+<style lang="scss" module>
+.root {
+ position: absolute;
+}
+
+.bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.spot {
+ --x: v-bind("spotX + 'px'");
+ --y: v-bind("spotY + 'px'");
+ --width: v-bind("spotWidth + 'px'");
+ --height: v-bind("spotHeight + 'px'");
+ --padding: 8px;
+ position: absolute;
+ left: calc(var(--x) - var(--padding));
+ top: calc(var(--y) - var(--padding));
+ width: calc(var(--width) + var(--padding) * 2);
+ height: calc(var(--height) + var(--padding) * 2);
+ box-sizing: border-box;
+ border: 1px solid transparent;
+ border-radius: 8px;
+ box-shadow: 0 0 0 9999px #000a;
+ transition: left 0.2s ease-out, top 0.2s ease-out, width 0.2s ease-out, height 0.2s ease-out;
+ animation: blink 1s infinite;
+}
+
+.body {
+ position: absolute;
+ padding: 16px 20px;
+ box-sizing: border-box;
+ width: max-content;
+ max-width: min(500px, 100vw);
+}
+
+@keyframes blink {
+ 0%, 100% {
+ background: color(from var(--MI_THEME-accent) srgb r g b / 0.1);
+ border: 1px solid color(from var(--MI_THEME-accent) srgb r g b / 0.75);
+ }
+ 50% {
+ background: transparent;
+ border: 1px solid transparent;
+ }
+}
+</style>