diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-28 20:07:37 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-28 20:07:37 +0900 |
| commit | e8005c8d3a6edf2c8cdce3fe098fb9acff8a57c6 (patch) | |
| tree | 4283a0a36f5cb03f0fb3a534142c06783f8ff725 /packages/client/src/components/ui | |
| parent | /antennas/notes API で日付による絞り込みができるようにする... (diff) | |
| download | sharkey-e8005c8d3a6edf2c8cdce3fe098fb9acff8a57c6.tar.gz sharkey-e8005c8d3a6edf2c8cdce3fe098fb9acff8a57c6.tar.bz2 sharkey-e8005c8d3a6edf2c8cdce3fe098fb9acff8a57c6.zip | |
client: refine ui
Diffstat (limited to 'packages/client/src/components/ui')
| -rw-r--r-- | packages/client/src/components/ui/button.vue | 4 | ||||
| -rw-r--r-- | packages/client/src/components/ui/menu.vue | 8 | ||||
| -rw-r--r-- | packages/client/src/components/ui/popup-menu.vue | 4 | ||||
| -rw-r--r-- | packages/client/src/components/ui/popup.vue | 198 | ||||
| -rw-r--r-- | packages/client/src/components/ui/super-menu.vue | 2 | ||||
| -rw-r--r-- | packages/client/src/components/ui/tooltip.vue | 63 |
6 files changed, 167 insertions, 112 deletions
diff --git a/packages/client/src/components/ui/button.vue b/packages/client/src/components/ui/button.vue index b5f4547c84..804a2e2720 100644 --- a/packages/client/src/components/ui/button.vue +++ b/packages/client/src/components/ui/button.vue @@ -142,12 +142,12 @@ export default defineComponent({ padding: 8px 14px; text-align: center; font-weight: normal; - font-size: 0.8em; + font-size: 0.9em; line-height: 22px; box-shadow: none; text-decoration: none; background: var(--buttonBg); - border-radius: 4px; + border-radius: 5px; overflow: clip; box-sizing: border-box; transition: background 0.1s ease; diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue index 0e237432ae..6ca5e32555 100644 --- a/packages/client/src/components/ui/menu.vue +++ b/packages/client/src/components/ui/menu.vue @@ -2,7 +2,7 @@ <div ref="items" v-hotkey="keymap" class="rrevdjwt" :class="{ center: align === 'center' }" - :style="{ width: width ? width + 'px' : null }" + :style="{ width: width ? width + 'px' : null, maxHeight: maxHeight ? maxHeight + 'px' : null }" @contextmenu.self="e => e.preventDefault()" > <template v-for="(item, i) in items2"> @@ -64,6 +64,10 @@ export default defineComponent({ type: Number, required: false }, + maxHeight: { + type: Number, + required: false + }, }, emits: ['close'], data() { @@ -146,8 +150,8 @@ export default defineComponent({ <style lang="scss" scoped> .rrevdjwt { padding: 8px 0; + box-sizing: border-box; min-width: 200px; - max-height: 90vh; overflow: auto; &.center { diff --git a/packages/client/src/components/ui/popup-menu.vue b/packages/client/src/components/ui/popup-menu.vue index ae2c044282..93bafddaee 100644 --- a/packages/client/src/components/ui/popup-menu.vue +++ b/packages/client/src/components/ui/popup-menu.vue @@ -1,6 +1,6 @@ <template> -<MkPopup ref="popup" :src="src" @closed="$emit('closed')"> - <MkMenu :items="items" :align="align" :width="width" class="_popup _shadow" @close="$refs.popup.close()"/> +<MkPopup ref="popup" v-slot="{ maxHeight, close }" :src="src" @closed="$emit('closed')"> + <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" class="_popup _shadow" @close="close()"/> </MkPopup> </template> diff --git a/packages/client/src/components/ui/popup.vue b/packages/client/src/components/ui/popup.vue index 77e928542d..2374de2eaf 100644 --- a/packages/client/src/components/ui/popup.vue +++ b/packages/client/src/components/ui/popup.vue @@ -1,15 +1,15 @@ <template> -<transition :name="$store.state.animation ? 'popup-menu' : ''" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered"> +<transition :name="$store.state.animation ? 'popup-menu' : ''" appear @after-leave="$emit('closed')" @enter="$emit('opening')"> <div v-show="manualShowing != null ? manualShowing : showing" ref="content" class="ccczpooj" :class="{ front, fixed, top: position === 'top' }" :style="{ pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> - <slot></slot> + <slot :max-height="maxHeight" :close="close"></slot> </div> </transition> </template> <script lang="ts"> -import { defineComponent, PropType } from 'vue'; +import { defineComponent, nextTick, onMounted, onUnmounted, PropType, ref, watch } from 'vue'; -function getFixedContainer(el: Element | null): Element | null { +function getFixedContainer(el: Element | null | undefined): Element | null { if (el == null || el.tagName === 'BODY') return null; const position = window.getComputedStyle(el).getPropertyValue('position'); if (position === 'fixed') { @@ -41,55 +41,40 @@ export default defineComponent({ type: Boolean, required: false, default: false, - } + }, + noOverlap: { + type: Boolean, + required: false, + default: true, + }, }, emits: ['opening', 'click', 'esc', 'close', 'closed'], - data() { - return { - showing: true, - fixed: false, - transformOrigin: 'center', - contentClicking: false, - }; - }, - - mounted() { - this.$watch('src', () => { - if (this.src) { - // eslint-disable-next-line vue/no-mutating-props - this.src.style.pointerEvents = 'none'; - } - this.fixed = getFixedContainer(this.src) != null; - this.$nextTick(() => { - this.align(); - }); - }, { immediate: true }); - - this.$nextTick(() => { - const popover = this.$refs.content as any; - new ResizeObserver((entries, observer) => { - this.align(); - }).observe(popover); - }); + setup(props, context) { + const maxHeight = ref<number>(); + const fixed = ref(false); + const transformOrigin = ref('center'); + const showing = ref(true); + const content = ref<HTMLElement>(); - document.addEventListener('mousedown', this.onDocumentClick, { passive: true }); - }, + const close = () => { + // eslint-disable-next-line vue/no-mutating-props + if (props.src) props.src.style.pointerEvents = 'auto'; + showing.value = false; + context.emit('close'); + }; - beforeUnmount() { - document.removeEventListener('mousedown', this.onDocumentClick); - }, + const MARGIN = 16; - methods: { - align() { - if (this.src == null) return; + const align = () => { + if (props.src == null) return; - const popover = this.$refs.content as any; + const popover = content.value!; if (popover == null) return; - const rect = this.src.getBoundingClientRect(); + const rect = props.src.getBoundingClientRect(); const width = popover.offsetWidth; const height = popover.offsetHeight; @@ -97,81 +82,84 @@ export default defineComponent({ let left; let top; - if (this.srcCenter) { - const x = rect.left + (this.fixed ? 0 : window.pageXOffset) + (this.src.offsetWidth / 2); - const y = rect.top + (this.fixed ? 0 : window.pageYOffset) + (this.src.offsetHeight / 2); + if (props.srcCenter) { + const x = rect.left + (fixed.value ? 0 : window.pageXOffset) + (props.src.offsetWidth / 2); + const y = rect.top + (fixed.value ? 0 : window.pageYOffset) + (props.src.offsetHeight / 2); left = (x - (width / 2)); top = (y - (height / 2)); } else { - const x = rect.left + (this.fixed ? 0 : window.pageXOffset) + (this.src.offsetWidth / 2); - const y = rect.top + (this.fixed ? 0 : window.pageYOffset) + this.src.offsetHeight; + const x = rect.left + (fixed.value ? 0 : window.pageXOffset) + (props.src.offsetWidth / 2); + const y = rect.top + (fixed.value ? 0 : window.pageYOffset) + props.src.offsetHeight; left = (x - (width / 2)); top = y; } - if (this.fixed) { + if (fixed.value) { + // 画面から横にはみ出る場合 if (left + width > window.innerWidth) { left = window.innerWidth - width; } - if (top + height > window.innerHeight) { - top = window.innerHeight - height; + // 画面から縦にはみ出る場合 + if (top + height > (window.innerHeight - MARGIN)) { + if (props.noOverlap) { + const underSpace = (window.innerHeight - MARGIN) - top; + const upperSpace = (rect.top - MARGIN); + if (underSpace >= (upperSpace / 3)) { + maxHeight.value = underSpace; + } else { + maxHeight.value = upperSpace; + top = (upperSpace + MARGIN) - height; + } + } else { + top = (window.innerHeight - MARGIN) - height; + } } } else { + // 画面から横にはみ出る場合 if (left + width - window.pageXOffset > window.innerWidth) { left = window.innerWidth - width + window.pageXOffset - 1; } - if (top + height - window.pageYOffset > window.innerHeight) { - top = window.innerHeight - height + window.pageYOffset - 1; + // 画面から縦にはみ出る場合 + if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) { + if (props.noOverlap) { + const underSpace = (window.innerHeight - MARGIN) - (top - window.pageYOffset); + const upperSpace = (rect.top - MARGIN); + if (underSpace >= (upperSpace / 3)) { + maxHeight.value = underSpace; + } else { + maxHeight.value = upperSpace; + top = window.pageYOffset + ((upperSpace + MARGIN) - height); + } + } else { + top = (window.innerHeight - MARGIN) - height + window.pageYOffset - 1; + } } } if (top < 0) { - top = 0; + top = MARGIN; } if (left < 0) { left = 0; } - if (top > rect.top + (this.fixed ? 0 : window.pageYOffset)) { - this.transformOrigin = 'center top'; + if (top > rect.top + (fixed.value ? 0 : window.pageYOffset)) { + transformOrigin.value = 'center top'; + } else if ((top + height) <= rect.top + (fixed.value ? 0 : window.pageYOffset)) { + transformOrigin.value = 'center bottom'; } else { - this.transformOrigin = 'center'; + transformOrigin.value = 'center'; } popover.style.left = left + 'px'; popover.style.top = top + 'px'; - }, - - childRendered() { - // モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する - const content = this.$refs.content.children[0]; - content.addEventListener('mousedown', e => { - this.contentClicking = true; - window.addEventListener('mouseup', e => { - // click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ - setTimeout(() => { - this.contentClicking = false; - }, 100); - }, { passive: true, once: true }); - }, { passive: true }); - }, - - close() { - // eslint-disable-next-line vue/no-mutating-props - if (this.src) this.src.style.pointerEvents = 'auto'; - this.showing = false; - this.$emit('close'); - }, - - onClosed() { - this.$emit('closed'); - }, + }; - onDocumentClick(ev) { - const flyoutElement = this.$refs.content; + const onDocumentClick = (ev: MouseEvent) => { + const flyoutElement = content.value; let targetElement = ev.target; do { if (targetElement === flyoutElement) { @@ -179,9 +167,45 @@ export default defineComponent({ } targetElement = targetElement.parentNode; } while (targetElement); - this.close(); - } - } + close(); + }; + + onMounted(() => { + watch(() => props.src, async () => { + if (props.src) { + // eslint-disable-next-line vue/no-mutating-props + props.src.style.pointerEvents = 'none'; + } + fixed.value = getFixedContainer(props.src) != null; + + await nextTick() + + align(); + }, { immediate: true, }); + + nextTick(() => { + const popover = content.value; + new ResizeObserver((entries, observer) => { + align(); + }).observe(popover!); + }); + + document.addEventListener('mousedown', onDocumentClick, { passive: true }); + + onUnmounted(() => { + document.removeEventListener('mousedown', onDocumentClick); + }); + }); + + return { + showing, + fixed, + content, + transformOrigin, + maxHeight, + close, + }; + }, }); </script> diff --git a/packages/client/src/components/ui/super-menu.vue b/packages/client/src/components/ui/super-menu.vue index b438d5dbc8..cb2154c48d 100644 --- a/packages/client/src/components/ui/super-menu.vue +++ b/packages/client/src/components/ui/super-menu.vue @@ -51,7 +51,6 @@ export default defineComponent({ } > .title { - font-size: 0.9em; opacity: 0.7; margin: 0 0 8px 12px; } @@ -64,7 +63,6 @@ export default defineComponent({ box-sizing: border-box; padding: 10px 16px 10px 8px; border-radius: 9px; - font-size: 0.9em; &:hover { text-decoration: none; diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue index dbc1a1c9b7..2a63c207fd 100644 --- a/packages/client/src/components/ui/tooltip.vue +++ b/packages/client/src/components/ui/tooltip.vue @@ -1,13 +1,13 @@ <template> <transition name="tooltip" appear @after-leave="$emit('closed')"> - <div v-show="showing" ref="content" class="buebdbiu _acrylic _shadow" :style="{ maxWidth: maxWidth + 'px' }"> + <div v-show="showing" ref="el" class="buebdbiu _acrylic _shadow" :style="{ maxWidth: maxWidth + 'px' }"> <slot>{{ text }}</slot> </div> </transition> </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, nextTick, onMounted, onUnmounted, ref } from 'vue'; export default defineComponent({ props: { @@ -31,35 +31,64 @@ export default defineComponent({ emits: ['closed'], - mounted() { - this.$nextTick(() => { - if (this.source == null) { - this.$emit('closed'); - return; - } + setup(props, context) { + const el = ref<HTMLElement>(); + + const setPosition = () => { + if (el.value == null) return; - const rect = this.source.getBoundingClientRect(); + const rect = props.source.getBoundingClientRect(); - const contentWidth = this.$refs.content.offsetWidth; - const contentHeight = this.$refs.content.offsetHeight; + const contentWidth = el.value.offsetWidth; + const contentHeight = el.value.offsetHeight; - let left = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); + let left = rect.left + window.pageXOffset + (props.source.offsetWidth / 2); let top = rect.top + window.pageYOffset - contentHeight; - left -= (this.$el.offsetWidth / 2); + left -= (el.value.offsetWidth / 2); if (left + contentWidth - window.pageXOffset > window.innerWidth) { left = window.innerWidth - contentWidth + window.pageXOffset - 1; } if (top - window.pageYOffset < 0) { - top = rect.top + window.pageYOffset + this.source.offsetHeight; - this.$refs.content.style.transformOrigin = 'center top'; + top = rect.top + window.pageYOffset + props.source.offsetHeight; + el.value.style.transformOrigin = 'center top'; } - this.$el.style.left = left + 'px'; - this.$el.style.top = top + 'px'; + el.value.style.left = left + 'px'; + el.value.style.top = top + 'px'; + }; + + onMounted(() => { + nextTick(() => { + if (props.source == null) { + context.emit('closed'); + return; + } + + setPosition(); + + let loopHandler; + + const loop = () => { + loopHandler = window.requestAnimationFrame(() => { + setPosition(); + loop(); + }); + }; + + loop(); + + onUnmounted(() => { + window.cancelAnimationFrame(loopHandler); + }); + }); }); + + return { + el, + }; }, }) </script> |