summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components/MkMenu.vue
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2025-03-16 14:05:58 +0900
committerGitHub <noreply@github.com>2025-03-16 05:05:58 +0000
commitce6b2448ced1101a4f0f8eeeb0d48378d40f696a (patch)
treef85f723dbc8ee84ec8051447cc212a07af5f19cf /packages/frontend/src/components/MkMenu.vue
parentNew Crowdin updates (#15662) (diff)
downloadsharkey-ce6b2448ced1101a4f0f8eeeb0d48378d40f696a.tar.gz
sharkey-ce6b2448ced1101a4f0f8eeeb0d48378d40f696a.tar.bz2
sharkey-ce6b2448ced1101a4f0f8eeeb0d48378d40f696a.zip
enhance(frontend): 投稿フォームの設定メニューを改良 (#14804)
* enhance(frontend): 投稿フォームの設定メニューを改良 * Update Changelog * indent * MkMenuのitemを切り出して共通化 * remove unused expose * fix: ドロワーなどのOptionが当たらない問題を修正 * 他のpopupMenuの項目選択時と挙動をあわせる * チュートリアルで詰む問題を修正 * Revert "MkMenuのitemを切り出して共通化" This reverts commit ce3679798c5b642dd0e47056f70657e46b382b5e. * enhance: slotで共通化 * Update MkPostFormOtherMenu.vue * remove duplicated locale key * refactor: メニューの定義をMkPostForm側で行うように * Update CHANGELOG.md * [ci skip] Update MkPostFormOtherMenu.vue * Update MkPostForm.vue * Update CHANGELOG.md --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
Diffstat (limited to 'packages/frontend/src/components/MkMenu.vue')
-rw-r--r--packages/frontend/src/components/MkMenu.vue333
1 files changed, 173 insertions, 160 deletions
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index aa53c19c33..954cfa58be 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -15,9 +15,6 @@ SPDX-License-Identifier: AGPL-3.0-only
@focusin.passive.stop="() => {}"
>
<div
- ref="itemsEl"
- v-hotkey="keymap"
- tabindex="0"
class="_popup _shadow"
:class="$style.menu"
:style="{
@@ -27,147 +24,156 @@ SPDX-License-Identifier: AGPL-3.0-only
@keydown.stop="() => {}"
@contextmenu.self.prevent="() => {}"
>
- <template v-for="item in (items2 ?? [])">
- <div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div>
- <span v-else-if="item.type === 'label'" role="menuitem" tabindex="-1" :class="[$style.label, $style.item]">
- <span style="opacity: 0.7;">{{ item.text }}</span>
+ <slot name="header"></slot>
+ <div
+ ref="itemsEl"
+ v-hotkey="keymap"
+ tabindex="0"
+ :class="$style.menuItems"
+ >
+ <template v-for="item in (items2 ?? [])">
+ <div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div>
+ <span v-else-if="item.type === 'label'" role="menuitem" tabindex="-1" :class="[$style.label, $style.item]">
+ <span style="opacity: 0.7;">{{ item.text }}</span>
+ </span>
+ <span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
+ <span><MkEllipsis/></span>
+ </span>
+ <MkA
+ v-else-if="item.type === 'link'"
+ role="menuitem"
+ tabindex="0"
+ :class="['_button', $style.item]"
+ :to="item.to"
+ @click.passive="close(true)"
+ @mouseenter.passive="onItemMouseEnter"
+ @mouseleave.passive="onItemMouseLeave"
+ >
+ <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
+ <MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
+ <div :class="$style.item_content">
+ <span :class="$style.item_content_text">{{ item.text }}</span>
+ <span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
+ </div>
+ </MkA>
+ <a
+ v-else-if="item.type === 'a'"
+ role="menuitem"
+ tabindex="0"
+ :class="['_button', $style.item]"
+ :href="item.href"
+ :target="item.target"
+ :rel="item.target === '_blank' ? 'noopener noreferrer' : undefined"
+ :download="item.download"
+ @click.passive="close(true)"
+ @mouseenter.passive="onItemMouseEnter"
+ @mouseleave.passive="onItemMouseLeave"
+ >
+ <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
+ <div :class="$style.item_content">
+ <span :class="$style.item_content_text">{{ item.text }}</span>
+ <span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
+ </div>
+ </a>
+ <button
+ v-else-if="item.type === 'user'"
+ role="menuitem"
+ tabindex="0"
+ :class="['_button', $style.item, { [$style.active]: item.active }]"
+ @click.prevent="item.active ? close(false) : clicked(item.action, $event)"
+ @mouseenter.passive="onItemMouseEnter"
+ @mouseleave.passive="onItemMouseLeave"
+ >
+ <MkAvatar :user="item.user" :class="$style.avatar"/><MkUserName :user="item.user"/>
+ <div v-if="item.indicate" :class="$style.item_content">
+ <span :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
+ </div>
+ </button>
+ <button
+ v-else-if="item.type === 'switch'"
+ role="menuitemcheckbox"
+ tabindex="0"
+ :class="['_button', $style.item]"
+ :disabled="unref(item.disabled)"
+ @click.prevent="switchItem(item)"
+ @mouseenter.passive="onItemMouseEnter"
+ @mouseleave.passive="onItemMouseLeave"
+ >
+ <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
+ <MkSwitchButton v-else :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
+ <div :class="$style.item_content">
+ <span :class="[$style.item_content_text, { [$style.switchText]: !item.icon }]">{{ item.text }}</span>
+ <MkSwitchButton v-if="item.icon" :class="[$style.switchButton, $style.caret]" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
+ </div>
+ </button>
+ <button
+ v-else-if="item.type === 'radio'"
+ role="menuitem"
+ tabindex="0"
+ :class="['_button', $style.item, $style.parent, { [$style.active]: childShowingItem === item }]"
+ :disabled="unref(item.disabled)"
+ @mouseenter.prevent="preferClick ? null : showRadioOptions(item, $event)"
+ @keydown.enter.prevent="preferClick ? null : showRadioOptions(item, $event)"
+ @click.prevent="!preferClick ? null : showRadioOptions(item, $event)"
+ >
+ <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
+ <div :class="$style.item_content">
+ <span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
+ <span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
+ </div>
+ </button>
+ <button
+ v-else-if="item.type === 'radioOption'"
+ role="menuitemradio"
+ tabindex="0"
+ :class="['_button', $style.item, $style.radio, { [$style.active]: unref(item.active) }]"
+ @click.prevent="unref(item.active) ? null : clicked(item.action, $event, false)"
+ @mouseenter.passive="onItemMouseEnter"
+ @mouseleave.passive="onItemMouseLeave"
+ >
+ <div :class="$style.icon">
+ <span :class="[$style.radioIcon, { [$style.radioChecked]: unref(item.active) }]"></span>
+ </div>
+ <div :class="$style.item_content">
+ <span :class="$style.item_content_text">{{ item.text }}</span>
+ </div>
+ </button>
+ <button
+ v-else-if="item.type === 'parent'"
+ role="menuitem"
+ tabindex="0"
+ :class="['_button', $style.item, $style.parent, { [$style.active]: childShowingItem === item }]"
+ @mouseenter.prevent="preferClick ? null : showChildren(item, $event)"
+ @keydown.enter.prevent="preferClick ? null : showChildren(item, $event)"
+ @click.prevent="!preferClick ? null : showChildren(item, $event)"
+ >
+ <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
+ <div :class="$style.item_content">
+ <span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
+ <span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
+ </div>
+ </button>
+ <button
+ v-else role="menuitem"
+ tabindex="0"
+ :class="['_button', $style.item, { [$style.danger]: item.danger, [$style.active]: unref(item.active) }]"
+ @click.prevent="unref(item.active) ? close(false) : clicked(item.action, $event)"
+ @mouseenter.passive="onItemMouseEnter"
+ @mouseleave.passive="onItemMouseLeave"
+ >
+ <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
+ <MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
+ <div :class="$style.item_content">
+ <span :class="$style.item_content_text">{{ item.text }}</span>
+ <span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
+ </div>
+ </button>
+ </template>
+ <span v-if="items2 == null || items2.length === 0" tabindex="-1" :class="[$style.none, $style.item]">
+ <span>{{ i18n.ts.none }}</span>
</span>
- <span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
- <span><MkEllipsis/></span>
- </span>
- <MkA
- v-else-if="item.type === 'link'"
- role="menuitem"
- tabindex="0"
- :class="['_button', $style.item]"
- :to="item.to"
- @click.passive="close(true)"
- @mouseenter.passive="onItemMouseEnter"
- @mouseleave.passive="onItemMouseLeave"
- >
- <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
- <MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
- <div :class="$style.item_content">
- <span :class="$style.item_content_text">{{ item.text }}</span>
- <span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
- </div>
- </MkA>
- <a
- v-else-if="item.type === 'a'"
- role="menuitem"
- tabindex="0"
- :class="['_button', $style.item]"
- :href="item.href"
- :target="item.target"
- :rel="item.target === '_blank' ? 'noopener noreferrer' : undefined"
- :download="item.download"
- @click.passive="close(true)"
- @mouseenter.passive="onItemMouseEnter"
- @mouseleave.passive="onItemMouseLeave"
- >
- <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
- <div :class="$style.item_content">
- <span :class="$style.item_content_text">{{ item.text }}</span>
- <span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
- </div>
- </a>
- <button
- v-else-if="item.type === 'user'"
- role="menuitem"
- tabindex="0"
- :class="['_button', $style.item, { [$style.active]: item.active }]"
- @click.prevent="item.active ? close(false) : clicked(item.action, $event)"
- @mouseenter.passive="onItemMouseEnter"
- @mouseleave.passive="onItemMouseLeave"
- >
- <MkAvatar :user="item.user" :class="$style.avatar"/><MkUserName :user="item.user"/>
- <div v-if="item.indicate" :class="$style.item_content">
- <span :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
- </div>
- </button>
- <button
- v-else-if="item.type === 'switch'"
- role="menuitemcheckbox"
- tabindex="0"
- :class="['_button', $style.item]"
- :disabled="unref(item.disabled)"
- @click.prevent="switchItem(item)"
- @mouseenter.passive="onItemMouseEnter"
- @mouseleave.passive="onItemMouseLeave"
- >
- <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
- <MkSwitchButton v-else :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
- <div :class="$style.item_content">
- <span :class="[$style.item_content_text, { [$style.switchText]: !item.icon }]">{{ item.text }}</span>
- <MkSwitchButton v-if="item.icon" :class="[$style.switchButton, $style.caret]" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
- </div>
- </button>
- <button
- v-else-if="item.type === 'radio'"
- role="menuitem"
- tabindex="0"
- :class="['_button', $style.item, $style.parent, { [$style.active]: childShowingItem === item }]"
- :disabled="unref(item.disabled)"
- @mouseenter.prevent="preferClick ? null : showRadioOptions(item, $event)"
- @keydown.enter.prevent="preferClick ? null : showRadioOptions(item, $event)"
- @click.prevent="!preferClick ? null : showRadioOptions(item, $event)"
- >
- <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
- <div :class="$style.item_content">
- <span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
- <span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
- </div>
- </button>
- <button
- v-else-if="item.type === 'radioOption'"
- role="menuitemradio"
- tabindex="0"
- :class="['_button', $style.item, $style.radio, { [$style.active]: unref(item.active) }]"
- @click.prevent="unref(item.active) ? null : clicked(item.action, $event, false)"
- @mouseenter.passive="onItemMouseEnter"
- @mouseleave.passive="onItemMouseLeave"
- >
- <div :class="$style.icon">
- <span :class="[$style.radioIcon, { [$style.radioChecked]: unref(item.active) }]"></span>
- </div>
- <div :class="$style.item_content">
- <span :class="$style.item_content_text">{{ item.text }}</span>
- </div>
- </button>
- <button
- v-else-if="item.type === 'parent'"
- role="menuitem"
- tabindex="0"
- :class="['_button', $style.item, $style.parent, { [$style.active]: childShowingItem === item }]"
- @mouseenter.prevent="preferClick ? null : showChildren(item, $event)"
- @keydown.enter.prevent="preferClick ? null : showChildren(item, $event)"
- @click.prevent="!preferClick ? null : showChildren(item, $event)"
- >
- <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
- <div :class="$style.item_content">
- <span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
- <span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
- </div>
- </button>
- <button
- v-else role="menuitem"
- tabindex="0"
- :class="['_button', $style.item, { [$style.danger]: item.danger, [$style.active]: unref(item.active) }]"
- @click.prevent="unref(item.active) ? close(false) : clicked(item.action, $event)"
- @mouseenter.passive="onItemMouseEnter"
- @mouseleave.passive="onItemMouseLeave"
- >
- <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
- <MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
- <div :class="$style.item_content">
- <span :class="$style.item_content_text">{{ item.text }}</span>
- <span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
- </div>
- </button>
- </template>
- <span v-if="items2 == null || items2.length === 0" tabindex="-1" :class="[$style.none, $style.item]">
- <span>{{ i18n.ts.none }}</span>
- </span>
+ </div>
+ <slot name="footer"></slot>
</div>
<div v-if="childMenu">
<XChild ref="child" :items="childMenu" :targetElement="childTarget!" :rootElement="itemsEl!" @actioned="childActioned" @closed="closeChild"/>
@@ -429,7 +435,7 @@ onBeforeUnmount(() => {
.root {
&.center {
> .menu {
- > .item {
+ .item {
text-align: center;
}
}
@@ -439,7 +445,7 @@ onBeforeUnmount(() => {
> .menu {
min-width: 230px;
- > .item {
+ .item {
padding: 6px 20px;
font-size: 0.95em;
line-height: 24px;
@@ -452,36 +458,38 @@ onBeforeUnmount(() => {
margin: auto;
> .menu {
- padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
width: 100%;
border-radius: 24px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
- > .item {
- font-size: 1em;
- padding: 12px 24px;
+ > .menuItems {
+ padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
- &::before {
- width: calc(100% - 24px);
- border-radius: 12px;
- }
+ > .item {
+ font-size: 1em;
+ padding: 12px 24px;
+
+ &::before {
+ width: calc(100% - 24px);
+ border-radius: 12px;
+ }
- > .icon {
- margin-right: 14px;
- width: 24px;
+ > .icon {
+ margin-right: 14px;
+ width: 24px;
+ }
}
- }
- > .divider {
- margin: 12px 0;
+ > .divider {
+ margin: 12px 0;
+ }
}
}
}
}
.menu {
- padding: 8px 0;
box-sizing: border-box;
max-width: 100vw;
min-width: 200px;
@@ -493,6 +501,11 @@ onBeforeUnmount(() => {
}
}
+.menuItems {
+ padding: 8px 0;
+ box-sizing: border-box;
+}
+
.item {
display: flex;
align-items: center;