feat: add dropdown component to refactor the action buttons (#3576)

#### What type of PR is this?

/kind improvement
/area console
/milestone 2.4.x

#### What this PR does / why we need it:

在 `@halo-dev/components` 添加 Dropdown 组件用于统一 Console 端所有下拉框的样式和表现,同时方便插件使用。

<img width="1408" alt="image" src="https://user-images.githubusercontent.com/21301288/227758168-ed6f40ef-8627-4d5a-8a4a-476badf5196d.png">
<img width="992" alt="image" src="https://user-images.githubusercontent.com/21301288/227758184-01bf61a0-f778-4ba8-aaea-f6b99715b577.png">

#### Which issue(s) this PR fixes:

Fixes #2987 

#### Does this PR introduce a user-facing change?

```release-note
Console 端添加 Dropdown 组件以统一所有下拉框的样式和表现。
```
pull/3561/head
Ryan Wang 2023-03-27 16:06:13 +08:00 committed by GitHub
parent fb2bc4252d
commit 9a00a74f06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 627 additions and 843 deletions

View File

@ -66,7 +66,6 @@
"dayjs": "^1.11.6",
"emoji-mart": "^5.3.3",
"fastq": "^1.15.0",
"floating-vue": "2.0.0-beta.20",
"fuse.js": "^6.6.2",
"jsencrypt": "^3.3.2",
"lodash.clonedeep": "^4.5.0",

View File

@ -71,6 +71,7 @@
"@codemirror/legacy-modes": "^6.3.0",
"@codemirror/state": "^6.1.4",
"@codemirror/view": "^6.5.1",
"codemirror": "^6.0.1"
"codemirror": "^6.0.1",
"floating-vue": "2.0.0-beta.20"
}
}

View File

@ -17,3 +17,5 @@ export * from "./components/status";
export * from "./components/entity";
export * from "./components/toast";
export * from "./components/loading";
export * from "./components/dropdown";
export * from "./components/tooltip";

View File

@ -0,0 +1,19 @@
<script lang="ts" setup>
import { VButton } from "@/components/button";
import { VDropdown, VDropdownItem, VDropdownDivider } from "./index";
</script>
<template>
<Story title="Dropdown">
<template #default>
<VDropdown>
<VButton>Hello</VButton>
<template #popper>
<VDropdownItem>删除</VDropdownItem>
<VDropdownDivider></VDropdownDivider>
<VDropdownItem>删除</VDropdownItem>
<VDropdownItem>编辑</VDropdownItem>
</template>
</VDropdown>
</template>
</Story>
</template>

View File

@ -0,0 +1,43 @@
<script lang="ts" setup>
import { Dropdown as FloatingDropdown, type Placement } from "floating-vue";
import "floating-vue/dist/style.css";
withDefaults(
defineProps<{
placement?: Placement;
triggers?: string[];
classes?: string[];
}>(),
{
placement: "bottom",
triggers: () => ["click"],
classes: () => [],
}
);
const emit = defineEmits<{
(event: "show"): void;
}>();
</script>
<template>
<FloatingDropdown
:placement="placement"
:triggers="triggers"
@show="emit('show')"
>
<slot />
<template #popper>
<div
class="min-w-[13rem] p-1.5"
:class="classes"
role="menu"
aria-orientation="vertical"
aria-labelledby="menu-button"
tabindex="-1"
>
<slot name="popper" />
</div>
</template>
</FloatingDropdown>
</template>

View File

@ -0,0 +1,5 @@
<script lang="ts" setup></script>
<template>
<div class="my-1 h-[1px] w-full bg-gray-100"></div>
</template>

View File

@ -0,0 +1,54 @@
<script lang="ts" setup>
import { VClosePopper } from "floating-vue";
withDefaults(
defineProps<{
selected?: boolean;
type: "default" | "danger";
}>(),
{
selected: false,
type: "default",
}
);
</script>
<template>
<div
v-close-popper
class="dropdown-item-wrapper"
:class="[`dropdown-item-wrapper--${type}${selected ? '--selected' : ''}`]"
role="menuitem"
tabindex="-1"
>
<div class="flex items-center gap-3">
<slot name="prefix-icon" />
<slot />
</div>
<slot name="suffix-icon"></slot>
</div>
</template>
<style lang="scss">
.dropdown-item-wrapper {
@apply flex w-full cursor-pointer justify-between items-center gap-1 rounded px-4 py-2 text-sm;
&--default {
@apply text-gray-700 hover:bg-gray-100 hover:text-gray-900;
&--selected {
@apply bg-gray-100 text-gray-900;
}
}
&--danger {
@apply text-red-500 hover:bg-red-50 hover:text-red-700;
&--selected {
@apply bg-red-50 text-red-700;
}
}
}
</style>

View File

@ -0,0 +1,4 @@
export { default as VDropdown } from "./Dropdown.vue";
export { default as VDropdownItem } from "./DropdownItem.vue";
export { default as VDropdownDivider } from "./DropdownDivider.vue";
export { VClosePopper } from "floating-vue";

View File

@ -1,7 +1,8 @@
<script lang="ts" setup>
import { VSpace } from "../space";
import { IconMore } from "../../icons/icons";
import { computed } from "vue";
import { VDropdown } from "../dropdown";
const props = withDefaults(
defineProps<{
isSelected?: boolean;
@ -35,7 +36,7 @@ const classes = computed(() => {
<slot name="end" />
</div>
<div v-if="$slots.dropdownItems" class="entity-dropdown">
<FloatingDropdown>
<VDropdown>
<div
class="entity-dropdown-trigger group-hover:bg-gray-200/60"
:class="{ '!bg-gray-300/60': isSelected }"
@ -44,13 +45,9 @@ const classes = computed(() => {
<IconMore />
</div>
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<slot name="dropdownItems"></slot>
</VSpace>
</div>
<slot name="dropdownItems"></slot>
</template>
</FloatingDropdown>
</VDropdown>
</div>
</div>
<div v-if="$slots.footer">

View File

@ -0,0 +1,2 @@
export { VTooltip } from "floating-vue";
import "./style.css";

View File

@ -0,0 +1,3 @@
.v-popper--theme-tooltip {
pointer-events: none;
}

View File

@ -18,6 +18,9 @@ export default defineConfig({
insertTypesEntry: true,
}),
],
define: {
"process.env": process.env,
},
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),

View File

@ -65,7 +65,6 @@ importers:
eslint-plugin-cypress: ^2.12.1
eslint-plugin-vue: ^9.8.0
fastq: ^1.15.0
floating-vue: 2.0.0-beta.20
fuse.js: ^6.6.2
husky: ^8.0.2
jsdom: ^20.0.3
@ -139,7 +138,6 @@ importers:
dayjs: 1.11.6
emoji-mart: 5.3.3
fastq: 1.15.0
floating-vue: 2.0.0-beta.20_vue@3.2.45
fuse.js: 6.6.2
jsencrypt: 3.3.2
lodash.clonedeep: 4.5.0
@ -243,6 +241,7 @@ importers:
'@histoire/plugin-vue': ^0.11.7
'@iconify-json/ri': ^1.1.4
codemirror: ^6.0.1
floating-vue: 2.0.0-beta.20
histoire: ^0.11.7
unplugin-icons: ^0.14.14
vite-plugin-dts: ^1.7.1
@ -259,6 +258,7 @@ importers:
'@codemirror/state': 6.1.4
'@codemirror/view': 6.5.1
codemirror: 6.0.1_@lezer+common@1.0.1
floating-vue: 2.0.0-beta.20_vue@3.2.45
vue: 3.2.45
vue-router: 4.1.6_vue@3.2.45
devDependencies:
@ -2645,8 +2645,8 @@ packages:
dependencies:
'@babel/parser': 7.21.2
'@babel/traverse': 7.21.2
'@intlify/message-compiler': 9.3.0-beta.16
'@intlify/shared': 9.3.0-beta.16
'@intlify/message-compiler': 9.3.0-beta.17
'@intlify/shared': 9.3.0-beta.17
jsonc-eslint-parser: 1.4.1
source-map: 0.6.1
vue-i18n: 9.2.2_vue@3.2.45
@ -2677,11 +2677,11 @@ packages:
'@intlify/shared': 9.2.2
source-map: 0.6.1
/@intlify/message-compiler/9.3.0-beta.16:
resolution: {integrity: sha512-CGQI3xRcs1ET75eDQ0DUy3MRYOqTauRIIgaMoISKiF83gqRWg93FqN8lGMKcpBqaF4tI0JhsfosCaGiBL9+dnw==}
/@intlify/message-compiler/9.3.0-beta.17:
resolution: {integrity: sha512-i7hvVIRk1Ax2uKa9xLRJCT57to08OhFMhFXXjWN07rmx5pWQYQ23MfX1xgggv9drnWTNhqEiD+u4EJeHoS5+Ww==}
engines: {node: '>= 14'}
dependencies:
'@intlify/shared': 9.3.0-beta.16
'@intlify/shared': 9.3.0-beta.17
source-map: 0.6.1
dev: true
@ -2689,8 +2689,8 @@ packages:
resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==}
engines: {node: '>= 14'}
/@intlify/shared/9.3.0-beta.16:
resolution: {integrity: sha512-kXbm4svALe3lX+EjdJxfnabOphqS4yQ1Ge/iIlR8tvUiYRCoNz3hig1M4336iY++Dfx5ytEQJPNjIcknNIuvig==}
/@intlify/shared/9.3.0-beta.17:
resolution: {integrity: sha512-mscf7RQsUTOil35jTij4KGW1RC9SWQjYScwLxP53Ns6g24iEd5HN7ksbt9O6FvTmlQuX77u+MXpBdfJsGqizLQ==}
engines: {node: '>= 14'}
dev: true
@ -2710,7 +2710,7 @@ packages:
optional: true
dependencies:
'@intlify/bundle-utils': 5.0.1_vue-i18n@9.2.2
'@intlify/shared': 9.3.0-beta.16
'@intlify/shared': 9.3.0-beta.17
'@rollup/pluginutils': 5.0.2_rollup@2.79.1
'@vue/compiler-sfc': 3.2.47
debug: 4.3.4
@ -2770,7 +2770,7 @@ packages:
resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==}
dependencies:
'@jridgewell/gen-mapping': 0.3.2
'@jridgewell/trace-mapping': 0.3.15
'@jridgewell/trace-mapping': 0.3.17
dev: true
/@jridgewell/sourcemap-codec/1.4.14:
@ -10097,7 +10097,7 @@ packages:
hasBin: true
dependencies:
'@jridgewell/source-map': 0.3.2
acorn: 8.8.1
acorn: 8.8.2
commander: 2.20.3
source-map-support: 0.5.21
dev: true

View File

@ -102,12 +102,4 @@ body {
*::-webkit-scrollbar-thumb:hover {
background-color: #bbb;
}
.v-popper__popper {
outline: none;
}
.v-popper--theme-tooltip {
pointer-events: none;
}
</style>

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { Category } from "@halo-dev/api-client";
import { VEntity, VEntityField } from "@halo-dev/components";
import { VEntity, VEntityField, VDropdown } from "@halo-dev/components";
import { setFocus } from "@/formkit/utils/focus";
import { computed, ref, watch } from "vue";
import Fuse from "fuse.js";
@ -71,7 +71,7 @@ const searchResults = computed(() => {
</script>
<template>
<FloatingDropdown @show="onDropdownShow">
<VDropdown :classes="['!p-0']" @show="onDropdownShow">
<slot />
<template #popper>
<div class="h-96 w-80">
@ -120,5 +120,5 @@ const searchResults = computed(() => {
</div>
</div>
</template>
</FloatingDropdown>
</VDropdown>
</template>

View File

@ -3,7 +3,12 @@ import {
useEditorExtensionPoints,
type EditorProvider,
} from "@/composables/use-editor-extension-points";
import { VAvatar, VSpace, IconExchange } from "@halo-dev/components";
import {
VAvatar,
IconExchange,
VDropdown,
VDropdownItem,
} from "@halo-dev/components";
withDefaults(
defineProps<{
@ -22,7 +27,7 @@ const { editorProviders } = useEditorExtensionPoints();
</script>
<template>
<FloatingDropdown>
<VDropdown>
<div
class="group flex w-full cursor-pointer items-center gap-2 rounded p-1 hover:bg-gray-100"
>
@ -33,30 +38,17 @@ const { editorProviders } = useEditorExtensionPoints();
<IconExchange class="h-4 w-4 text-gray-600 group-hover:text-gray-900" />
</div>
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<div
v-for="(editorProvider, index) in editorProviders"
:key="index"
v-close-popper
class="group flex w-full cursor-pointer items-center gap-2 rounded p-1 hover:bg-gray-100"
:class="{
'bg-gray-100': editorProvider.name === provider?.name,
}"
@click="emit('select', editorProvider)"
>
<VAvatar :src="editorProvider.logo" size="xs"></VAvatar>
<div
class="text-sm text-gray-600 group-hover:text-gray-900"
:class="{
'text-gray-900': editorProvider.name === provider?.name,
}"
>
{{ editorProvider.displayName }}
</div>
</div>
</VSpace>
</div>
<VDropdownItem
v-for="(editorProvider, index) in editorProviders"
:key="index"
:selected="provider?.name === editorProvider.name"
@click="emit('select', editorProvider)"
>
<template #prefix-icon>
<VAvatar :src="editorProvider.logo" size="xs"></VAvatar>
</template>
{{ editorProvider.displayName }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
</template>

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { Tag } from "@halo-dev/api-client";
import { VEntity, VEntityField } from "@halo-dev/components";
import { VEntity, VEntityField, VDropdown } from "@halo-dev/components";
import { setFocus } from "@/formkit/utils/focus";
import { computed, ref, watch } from "vue";
import Fuse from "fuse.js";
@ -69,7 +69,7 @@ const searchResults = computed(() => {
</script>
<template>
<FloatingDropdown @show="onDropdownShow">
<VDropdown :classes="['!p-0']" @show="onDropdownShow">
<slot />
<template #popper>
<div class="h-96 w-80">
@ -117,5 +117,5 @@ const searchResults = computed(() => {
</div>
</div>
</template>
</FloatingDropdown>
</VDropdown>
</template>

View File

@ -1,7 +1,12 @@
<script lang="ts" setup>
import type { User } from "@halo-dev/api-client";
import { useUserFetch } from "@/modules/system/users/composables/use-user";
import { VAvatar, VEntity, VEntityField } from "@halo-dev/components";
import {
VAvatar,
VDropdown,
VEntity,
VEntityField,
} from "@halo-dev/components";
import { setFocus } from "@/formkit/utils/focus";
import { computed, ref, watch } from "vue";
import Fuse from "fuse.js";
@ -66,7 +71,7 @@ const searchResults = computed(() => {
</script>
<template>
<FloatingDropdown @show="onDropdownShow">
<VDropdown :classes="['!p-0']" @show="onDropdownShow">
<slot />
<template #popper>
<div class="h-96 w-80">
@ -114,5 +119,5 @@ const searchResults = computed(() => {
</div>
</div>
</template>
</FloatingDropdown>
</VDropdown>
</template>

View File

@ -5,9 +5,9 @@ import {
IconUserSettings,
VTag,
VAvatar,
VSpace,
VButton,
Dialog,
VDropdown,
VDropdownItem,
} from "@halo-dev/components";
import { RoutesMenu } from "@/components/menu/RoutesMenu";
import type { MenuGroupType, MenuItemType } from "@halo-dev/console-shared";
@ -273,36 +273,26 @@ onMounted(() => {
</VTag>
</div>
</div>
<FloatingDropdown
<VDropdown
class="profile-control cursor-pointer rounded p-1 transition-all hover:bg-gray-100"
>
<IconMore />
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<VButton
v-close-popper
block
type="secondary"
:route="{
name: 'UserDetail',
params: { name: '-' },
}"
>
{{ $t("core.sidebar.operations.profile.button") }}
</VButton>
<VButton
v-close-popper
block
type="default"
@click="handleLogout"
>
{{ $t("core.sidebar.operations.logout.button") }}
</VButton>
</VSpace>
</div>
<VDropdownItem
@click="
$router.push({
name: 'UserDetail',
params: { name: '-' },
})
"
>
{{ $t("core.sidebar.operations.profile.button") }}
</VDropdownItem>
<VDropdownItem @click="handleLogout">
{{ $t("core.sidebar.operations.logout.button") }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
</div>
</div>
</aside>

View File

@ -21,6 +21,8 @@ import {
VEntityField,
VLoading,
Toast,
VDropdown,
VDropdownItem,
} from "@halo-dev/components";
import LazyImage from "@/components/image/LazyImage.vue";
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
@ -421,33 +423,25 @@ onMounted(() => {
$t("core.attachment.operations.deselect_items.button")
}}
</VButton>
<FloatingDropdown>
<VDropdown>
<VButton>
{{ $t("core.attachment.operations.move.button") }}
</VButton>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(group, index) in groups"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleMove(group)"
>
<span class="truncate">
{{ group.spec.displayName }}
</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(group, index) in groups"
:key="index"
@click="handleMove(group)"
>
{{ group.spec.displayName }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
</VSpace>
</div>
<div class="mt-4 flex sm:mt-0">
<VSpace spacing="lg">
<FloatingDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -461,28 +455,16 @@ onMounted(() => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(policy, index) in policies"
:key="index"
v-close-popper
:class="{
'bg-gray-100':
selectedPolicy?.metadata.name ===
policy.metadata.name,
}"
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleSelectPolicy(policy)"
>
<span class="truncate">
{{ policy.spec.displayName }}
</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(policy, index) in policies"
:key="index"
:selected="policy === selectedPolicy"
@click="handleSelectPolicy(policy)"
>
{{ policy.spec.displayName }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
<UserDropdownSelector
v-model:selected="selectedUser"
@select="handleSelectUser"
@ -499,7 +481,7 @@ onMounted(() => {
</div>
</UserDropdownSelector>
<!-- TODO: add filter by ref support -->
<FloatingDropdown v-if="false">
<VDropdown v-if="false">
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -509,25 +491,11 @@ onMounted(() => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">未被引用</span>
</li>
<li
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">文章</span>
</li>
</ul>
</div>
<VDropdownItem> 未被引用 </VDropdownItem>
<VDropdownItem> 文章 </VDropdownItem>
</template>
</FloatingDropdown>
<FloatingDropdown>
</VDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -539,21 +507,16 @@ onMounted(() => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(sortItem, index) in SortItems"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleSortItemChange(sortItem)"
>
<span class="truncate">{{ sortItem.label }}</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(sortItem, index) in SortItems"
:key="index"
:selected="sortItem.value === selectedSortItem?.value"
@click="handleSortItemChange(sortItem)"
>
{{ sortItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
<div class="flex flex-row gap-2">
<div
v-for="(item, index) in viewTypes"
@ -810,14 +773,12 @@ onMounted(() => {
"
#dropdownItems
>
<VButton
v-close-popper
block
<VDropdownItem
type="danger"
@click="handleDelete(attachment)"
>
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</li>

View File

@ -8,8 +8,8 @@ import {
IconAddCircle,
IconMore,
Toast,
VButton,
VSpace,
VDropdown,
VDropdownItem,
VStatusDot,
} from "@halo-dev/components";
import AttachmentGroupEditingModal from "./AttachmentGroupEditingModal.vue";
@ -255,67 +255,39 @@ onMounted(async () => {
animate
/>
</div>
<FloatingDropdown
v-if="!readonly"
v-permission="['system:attachments:manage']"
>
<VDropdown v-if="!readonly" v-permission="['system:attachments:manage']">
<IconMore @click.stop />
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<VButton
v-close-popper
block
type="secondary"
@click="handleOpenEditingModal(group)"
<VDropdownItem @click="handleOpenEditingModal(group)">
{{ $t("core.attachment.group_list.operations.rename.button") }}
</VDropdownItem>
<VDropdown placement="right" :triggers="['click']">
<VDropdownItem type="danger">
{{ $t("core.common.buttons.delete") }}
</VDropdownItem>
<template #popper>
<VDropdownItem
v-close-popper.all
type="danger"
@click="handleDelete(group)"
>
{{ $t("core.attachment.group_list.operations.rename.button") }}
</VButton>
<FloatingDropdown
class="w-full"
placement="right"
:triggers="['click']"
{{ $t("core.attachment.group_list.operations.delete.button") }}
</VDropdownItem>
<VDropdownItem
v-close-popper.all
type="danger"
@click="handleDeleteWithAttachments(group)"
>
<VButton block type="danger">
{{ $t("core.common.buttons.delete") }}
</VButton>
<template #popper>
<div class="w-52 p-2">
<VSpace class="w-full" direction="column">
<VButton
v-close-popper.all
block
type="danger"
size="sm"
@click="handleDelete(group)"
>
{{
$t(
"core.attachment.group_list.operations.delete.button"
)
}}
</VButton>
<VButton
v-close-popper.all
block
type="danger"
size="sm"
@click="handleDeleteWithAttachments(group)"
>
{{
$t(
"core.attachment.group_list.operations.delete_with_attachments.button"
)
}}
</VButton>
</VSpace>
</div>
</template>
</FloatingDropdown>
</VSpace>
</div>
{{
$t(
"core.attachment.group_list.operations.delete_with_attachments.button"
)
}}
</VDropdownItem>
</template>
</VDropdown>
</template>
</FloatingDropdown>
</VDropdown>
</div>
<div
v-if="!loading && !readonly"

View File

@ -9,6 +9,8 @@ import {
VEntity,
VEntityField,
VStatusDot,
VDropdown,
VDropdownItem,
Toast,
} from "@halo-dev/components";
import AttachmentPolicyEditingModal from "./AttachmentPolicyEditingModal.vue";
@ -126,28 +128,20 @@ const onEditingModalClose = () => {
@update:visible="onVisibleChange"
>
<template #actions>
<FloatingDropdown>
<VDropdown>
<span v-tooltip="$t('core.common.buttons.new')">
<IconAddCircle />
</span>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(policyTemplate, index) in policyTemplates"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleOpenCreateNewPolicyModal(policyTemplate)"
>
<span class="truncate">
{{ policyTemplate.spec?.displayName }}
</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(policyTemplate, index) in policyTemplates"
:key="index"
@click="handleOpenCreateNewPolicyModal(policyTemplate)"
>
{{ policyTemplate.spec?.displayName }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
</template>
<VEmpty
v-if="!policies?.length && !isLoading"
@ -159,7 +153,7 @@ const onEditingModalClose = () => {
<VButton @click="handleFetchPolicies">
{{ $t("core.common.buttons.refresh") }}
</VButton>
<FloatingDropdown>
<VDropdown>
<VButton type="secondary">
<template #icon>
<IconAddCircle class="h-full w-full" />
@ -167,23 +161,15 @@ const onEditingModalClose = () => {
{{ $t("core.common.buttons.new") }}
</VButton>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(policyTemplate, index) in policyTemplates"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleOpenCreateNewPolicyModal(policyTemplate)"
>
<span class="truncate">
{{ policyTemplate.spec?.displayName }}
</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(policyTemplate, index) in policyTemplates"
:key="index"
@click="handleOpenCreateNewPolicyModal(policyTemplate)"
>
{{ policyTemplate.spec?.displayName }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
</VSpace>
</template>
</VEmpty>
@ -219,22 +205,12 @@ const onEditingModalClose = () => {
</VEntityField>
</template>
<template #dropdownItems>
<VButton
v-close-popper
block
type="secondary"
@click="handleOpenEditingModal(policy)"
>
<VDropdownItem @click="handleOpenEditingModal(policy)">
{{ $t("core.common.buttons.edit") }}
</VButton>
<VButton
v-close-popper
block
type="danger"
@click="handleDelete(policy)"
>
</VDropdownItem>
<VDropdownItem type="danger" @click="handleDelete(policy)">
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</li>

View File

@ -1,5 +1,11 @@
<script lang="ts" setup>
import { VModal, IconAddCircle, VAlert } from "@halo-dev/components";
import {
VModal,
IconAddCircle,
VAlert,
VDropdown,
VDropdownItem,
} from "@halo-dev/components";
import UppyUpload from "@/components/upload/UppyUpload.vue";
import { ref, watch } from "vue";
import type { Policy, PolicyTemplate } from "@halo-dev/api-client";
@ -175,7 +181,7 @@ watch(
</div>
</div>
<FloatingDropdown>
<VDropdown>
<div
class="flex h-full cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm"
>
@ -187,23 +193,15 @@ watch(
<IconAddCircle />
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(policyTemplate, index) in policyTemplates"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleOpenCreateNewPolicyModal(policyTemplate)"
>
<span class="truncate">
{{ policyTemplate.spec?.displayName }}
</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(policyTemplate, index) in policyTemplates"
:key="index"
@click="handleOpenCreateNewPolicyModal(policyTemplate)"
>
{{ policyTemplate.spec?.displayName }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
</div>
<div v-if="!policies?.length" class="mb-3">
<VAlert

View File

@ -11,6 +11,8 @@ import {
VEmpty,
Dialog,
VLoading,
VDropdown,
VDropdownItem,
Toast,
} from "@halo-dev/components";
import CommentListItem from "./components/CommentListItem.vue";
@ -384,7 +386,7 @@ const handleApproveInBatch = async () => {
</div>
<div class="mt-4 flex sm:mt-0">
<VSpace spacing="lg">
<FloatingDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -396,28 +398,18 @@ const handleApproveInBatch = async () => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(filterItem, index) in ApprovedFilterItems"
:key="index"
v-close-popper
:class="{
'bg-gray-100':
selectedApprovedFilterItem.value ===
filterItem.value,
}"
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleApprovedFilterItemChange(filterItem)"
>
<span class="truncate">
{{ filterItem.label }}
</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(filterItem, index) in ApprovedFilterItems"
:key="index"
:selected="
selectedApprovedFilterItem.value === filterItem.value
"
@click="handleApprovedFilterItemChange(filterItem)"
>
{{ filterItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
<UserDropdownSelector
v-model:selected="selectedUser"
@select="handleSelectUser"
@ -433,7 +425,7 @@ const handleApproveInBatch = async () => {
</span>
</div>
</UserDropdownSelector>
<FloatingDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -445,27 +437,18 @@ const handleApproveInBatch = async () => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(filterItem, index) in SortFilterItems"
:key="index"
v-close-popper
:class="{
'bg-gray-100':
selectedSortFilterItem.value === filterItem.value,
}"
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleSortFilterItemChange(filterItem)"
>
<span class="truncate">
{{ filterItem.label }}
</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(filterItem, index) in SortFilterItems"
:key="index"
:selected="
selectedSortFilterItem.value === filterItem.value
"
@click="handleSortFilterItemChange(filterItem)"
>
{{ filterItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
<div class="flex flex-row gap-2">
<div
class="group cursor-pointer rounded p-1 hover:bg-gray-200"

View File

@ -12,6 +12,7 @@ import {
IconExternalLinkLine,
VLoading,
Toast,
VDropdownItem,
VTag,
} from "@halo-dev/components";
import ReplyCreationModal from "./ReplyCreationModal.vue";
@ -386,26 +387,18 @@ const subjectRefResult = computed(() => {
v-if="currentUserHasPermission(['system:comments:manage'])"
#dropdownItems
>
<VButton
<VDropdownItem
v-if="!comment?.comment.spec.approved"
v-close-popper
type="secondary"
block
@click="handleApprove"
>
{{ $t("core.comment.operations.approve_comment_in_batch.button") }}
</VButton>
<VButton
v-close-popper
type="secondary"
block
@click="handleApproveReplyInBatch"
>
</VDropdownItem>
<VDropdownItem @click="handleApproveReplyInBatch">
{{ $t("core.comment.operations.approve_applies_in_batch.button") }}
</VButton>
<VButton v-close-popper block type="danger" @click="handleDelete">
</VDropdownItem>
<VDropdownItem type="danger" @click="handleDelete">
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
<template v-if="showReplies" #footer>

View File

@ -5,6 +5,7 @@ import {
VButton,
IconMotionLine,
Toast,
VDropdown,
} from "@halo-dev/components";
import SubmitButton from "@/components/button/SubmitButton.vue";
import type {
@ -172,14 +173,14 @@ watchEffect(() => {
></FormKit>
</FormKit>
<div class="mt-2 flex justify-end">
<FloatingDropdown>
<VDropdown :classes="['!p-0']">
<IconMotionLine
class="h-5 w-5 cursor-pointer text-gray-500 transition-all hover:text-gray-900"
/>
<template #popper>
<div ref="emojiPickerRef"></div>
</template>
</FloatingDropdown>
</VDropdown>
</div>
<template #footer>
<VSpace>

View File

@ -1,12 +1,12 @@
<script lang="ts" setup>
import {
VAvatar,
VButton,
VTag,
VEntityField,
VEntity,
Dialog,
VStatusDot,
VDropdownItem,
IconReplyLine,
Toast,
} from "@halo-dev/components";
@ -196,25 +196,20 @@ const isHoveredReply = computed(() => {
</VEntityField>
</template>
<template #dropdownItems>
<VButton
<VDropdownItem
v-if="!reply?.reply.spec.approved"
v-permission="['system:comments:manage']"
v-close-popper
type="secondary"
block
@click="handleApprove"
>
{{ $t("core.comment.operations.approve_reply.button") }}
</VButton>
<VButton
</VDropdownItem>
<VDropdownItem
v-permission="['system:comments:manage']"
v-close-popper
block
type="danger"
@click="handleDelete"
>
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</template>

View File

@ -16,6 +16,7 @@ import {
VStatusDot,
VLoading,
Toast,
VDropdownItem,
} from "@halo-dev/components";
import { ref, watch } from "vue";
import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client";
@ -423,22 +424,15 @@ function handleClearKeyword() {
v-if="currentUserHasPermission(['system:singlepages:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block
<VDropdownItem
type="danger"
@click="handleDeletePermanently(singlePage.page)"
>
{{ $t("core.common.buttons.delete_permanently") }}
</VButton>
<VButton
v-close-popper
block
type="default"
@click="handleRecovery(singlePage.page)"
>
</VDropdownItem>
<VDropdownItem @click="handleRecovery(singlePage.page)">
{{ $t("core.common.buttons.recovery") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</li>

View File

@ -22,6 +22,8 @@ import {
VLoading,
VPageHeader,
Toast,
VDropdown,
VDropdownItem,
} from "@halo-dev/components";
import SinglePageSettingModal from "./components/SinglePageSettingModal.vue";
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
@ -547,7 +549,7 @@ watch(selectedPageNames, (newValue) => {
</div>
<div class="mt-4 flex sm:mt-0">
<VSpace spacing="lg">
<FloatingDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -559,27 +561,19 @@ watch(selectedPageNames, (newValue) => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(filterItem, index) in PublishStatusItems"
:key="index"
v-close-popper
:class="{
'bg-gray-100':
selectedPublishStatusItem.value ===
filterItem.value,
}"
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handlePublishStatusItemChange(filterItem)"
>
<span class="truncate">{{ filterItem.label }}</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(filterItem, index) in PublishStatusItems"
:key="index"
:selected="
filterItem.value === selectedPublishStatusItem.value
"
@click="handlePublishStatusItemChange(filterItem)"
>
{{ filterItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
<FloatingDropdown>
</VDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -591,27 +585,16 @@ watch(selectedPageNames, (newValue) => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(filterItem, index) in VisibleItems"
:key="index"
v-close-popper
:class="{
'bg-gray-100':
selectedVisibleItem.value === filterItem.value,
}"
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleVisibleItemChange(filterItem)"
>
<span class="truncate">
{{ filterItem.label }}
</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(filterItem, index) in VisibleItems"
:key="index"
:selected="filterItem.value === selectedVisibleItem.value"
@click="handleVisibleItemChange(filterItem)"
>
{{ filterItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
<UserDropdownSelector
v-model:selected="selectedContributor"
@select="handleSelectUser"
@ -627,7 +610,7 @@ watch(selectedPageNames, (newValue) => {
</span>
</div>
</UserDropdownSelector>
<FloatingDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -639,21 +622,19 @@ watch(selectedPageNames, (newValue) => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(sortItem, index) in SortItems"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleSortItemChange(sortItem)"
>
<span class="truncate">{{ sortItem.label }}</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(sortItem, index) in SortItems"
:key="index"
:selected="
sortItem.sort === selectedSortItem?.sort &&
sortItem.sortOrder === selectedSortItem?.sortOrder
"
@click="handleSortItemChange(sortItem)"
>
{{ sortItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
<div class="flex flex-row gap-2">
<div
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
@ -837,22 +818,15 @@ watch(selectedPageNames, (newValue) => {
v-if="currentUserHasPermission(['system:singlepages:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block
type="secondary"
@click="handleOpenSettingModal(singlePage.page)"
>
<VDropdownItem @click="handleOpenSettingModal(singlePage.page)">
{{ $t("core.common.buttons.setting") }}
</VButton>
<VButton
v-close-popper
block
</VDropdownItem>
<VDropdownItem
type="danger"
@click="handleDelete(singlePage.page)"
>
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</li>

View File

@ -16,6 +16,7 @@ import {
VEntityField,
VLoading,
Toast,
VDropdownItem,
} from "@halo-dev/components";
import PostTag from "./tags/components/PostTag.vue";
import { ref, watch } from "vue";
@ -435,22 +436,15 @@ function handleClearKeyword() {
v-if="currentUserHasPermission(['system:posts:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block
<VDropdownItem
type="danger"
@click="handleDeletePermanently(post.post)"
>
{{ $t("core.common.buttons.delete_permanently") }}
</VButton>
<VButton
v-close-popper
block
type="default"
@click="handleRecovery(post.post)"
>
</VDropdownItem>
<VDropdownItem @click="handleRecovery(post.post)">
{{ $t("core.common.buttons.recovery") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</li>

View File

@ -22,6 +22,8 @@ import {
VEntityField,
VLoading,
Toast,
VDropdownItem,
VDropdown,
} from "@halo-dev/components";
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
import CategoryDropdownSelector from "@/components/dropdown-selector/CategoryDropdownSelector.vue";
@ -580,7 +582,7 @@ watch(selectedPostNames, (newValue) => {
</div>
<div class="mt-4 flex sm:mt-0">
<VSpace spacing="lg">
<FloatingDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -592,27 +594,19 @@ watch(selectedPostNames, (newValue) => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(filterItem, index) in PublishStatusItems"
:key="index"
v-close-popper
:class="{
'bg-gray-100':
selectedPublishStatusItem.value ===
filterItem.value,
}"
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handlePublishStatusItemChange(filterItem)"
>
<span class="truncate">{{ filterItem.label }}</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(filterItem, index) in PublishStatusItems"
:key="index"
:selected="
filterItem.value === selectedPublishStatusItem.value
"
@click="handlePublishStatusItemChange(filterItem)"
>
{{ filterItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
<FloatingDropdown>
</VDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -624,27 +618,16 @@ watch(selectedPostNames, (newValue) => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(filterItem, index) in VisibleItems"
:key="index"
v-close-popper
:class="{
'bg-gray-100':
selectedVisibleItem.value === filterItem.value,
}"
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleVisibleItemChange(filterItem)"
>
<span class="truncate">
{{ filterItem.label }}
</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(filterItem, index) in VisibleItems"
:key="index"
:selected="filterItem.value === selectedVisibleItem.value"
@click="handleVisibleItemChange(filterItem)"
>
{{ filterItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
<CategoryDropdownSelector
v-model:selected="selectedCategory"
@select="handleCategoryChange"
@ -690,7 +673,7 @@ watch(selectedPostNames, (newValue) => {
</span>
</div>
</UserDropdownSelector>
<FloatingDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -702,21 +685,19 @@ watch(selectedPostNames, (newValue) => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(sortItem, index) in SortItems"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleSortItemChange(sortItem)"
>
<span class="truncate">{{ sortItem.label }}</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(sortItem, index) in SortItems"
:key="index"
:selected="
sortItem.sort === selectedSortItem?.sort &&
sortItem.sortOrder === selectedSortItem?.sortOrder
"
@click="handleSortItemChange(sortItem)"
>
{{ sortItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
<div class="flex flex-row gap-2">
<div
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
@ -932,22 +913,12 @@ watch(selectedPostNames, (newValue) => {
v-if="currentUserHasPermission(['system:posts:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block
type="secondary"
@click="handleOpenSettingModal(post.post)"
>
<VDropdownItem @click="handleOpenSettingModal(post.post)">
{{ $t("core.common.buttons.setting") }}
</VButton>
<VButton
v-close-popper
block
type="danger"
@click="handleDelete(post.post)"
>
</VDropdownItem>
<VDropdownItem type="danger" @click="handleDelete(post.post)">
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</li>

View File

@ -1,10 +1,10 @@
<script lang="ts" setup>
import {
IconList,
VButton,
VStatusDot,
VEntity,
VEntityField,
VDropdownItem,
} from "@halo-dev/components";
import Draggable from "vuedraggable";
import type { CategoryTree } from "../utils";
@ -111,24 +111,19 @@ function onDelete(category: CategoryTree) {
v-if="currentUserHasPermission(['system:posts:manage'])"
#dropdownItems
>
<VButton
<VDropdownItem
v-permission="['system:posts:manage']"
v-close-popper
block
type="secondary"
@click="onOpenEditingModal(category)"
>
{{ $t("core.common.buttons.edit") }}
</VButton>
<VButton
</VDropdownItem>
<VDropdownItem
v-permission="['system:posts:manage']"
v-close-popper
block
type="danger"
@click="onDelete(category)"
>
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
<CategoryListItem

View File

@ -17,6 +17,7 @@ import {
VEntity,
VEntityField,
VLoading,
VDropdownItem,
} from "@halo-dev/components";
import TagEditingModal from "./components/TagEditingModal.vue";
import PostTag from "./components/PostTag.vue";
@ -242,24 +243,19 @@ onMounted(async () => {
v-if="currentUserHasPermission(['system:posts:manage'])"
#dropdownItems
>
<VButton
<VDropdownItem
v-permission="['system:posts:manage']"
v-close-popper
block
type="secondary"
@click="handleOpenEditingModal(tag)"
>
{{ $t("core.common.buttons.edit") }}
</VButton>
<VButton
</VDropdownItem>
<VDropdownItem
v-permission="['system:posts:manage']"
v-close-popper
block
type="danger"
@click="handleDelete(tag)"
>
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</li>

View File

@ -1,11 +1,11 @@
<script lang="ts" setup>
import {
IconList,
VButton,
VTag,
VStatusDot,
VEntity,
VEntityField,
VDropdownItem,
} from "@halo-dev/components";
import Draggable from "vuedraggable";
import { ref } from "vue";
@ -130,30 +130,15 @@ function getMenuItemRefDisplayName(menuItem: MenuTreeItem) {
v-if="currentUserHasPermission(['system:menus:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block
type="secondary"
@click="onOpenEditingModal(menuItem)"
>
<VDropdownItem @click="onOpenEditingModal(menuItem)">
{{ $t("core.common.buttons.edit") }}
</VButton>
<VButton
v-close-popper
block
type="default"
@click="onOpenCreateByParentModal(menuItem)"
>
</VDropdownItem>
<VDropdownItem @click="onOpenCreateByParentModal(menuItem)">
{{ $t("core.menu.operations.add_sub_menu_item.button") }}
</VButton>
<VButton
v-close-popper
block
type="danger"
@click="onDelete(menuItem)"
>
</VDropdownItem>
<VDropdownItem type="danger" @click="onDelete(menuItem)">
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
<MenuItemListItem

View File

@ -11,6 +11,7 @@ import {
VTag,
VLoading,
Toast,
VDropdownItem,
} from "@halo-dev/components";
import MenuEditingModal from "./MenuEditingModal.vue";
import { onMounted, onUnmounted, ref } from "vue";
@ -258,30 +259,15 @@ onMounted(handleFetchPrimaryMenuName);
v-if="currentUserHasPermission(['system:menus:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block
type="secondary"
@click="handleSetPrimaryMenu(menu)"
>
<VDropdownItem @click="handleSetPrimaryMenu(menu)">
{{ $t("core.menu.operations.set_primary.button") }}
</VButton>
<VButton
v-close-popper
block
type="default"
@click="handleOpenEditingModal(menu)"
>
</VDropdownItem>
<VDropdownItem @click="handleOpenEditingModal(menu)">
{{ $t("core.common.buttons.edit") }}
</VButton>
<VButton
v-close-popper
block
type="danger"
@click="handleDeleteMenu(menu)"
>
</VDropdownItem>
<VDropdownItem type="danger" @click="handleDeleteMenu(menu)">
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</li>

View File

@ -10,11 +10,13 @@ import {
VSpace,
VTag,
IconMore,
VButton,
Dialog,
VAvatar,
Toast,
VStatusDot,
VDropdown,
VDropdownItem,
VDropdownDivider,
} from "@halo-dev/components";
import ThemeUploadModal from "./components/ThemeUploadModal.vue";
@ -102,43 +104,25 @@ const onUpgradeModalClose = () => {
</p>
</div>
</div>
<FloatingDropdown v-permission="['system:themes:manage']">
<VDropdown v-permission="['system:themes:manage']">
<div
class="cursor-pointer rounded p-1 transition-all hover:text-blue-600 group-hover:bg-gray-100"
>
<IconMore />
</div>
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<VButton
v-close-popper
block
type="secondary"
@click="upgradeModal = true"
>
{{ $t("core.common.buttons.upgrade") }}
</VButton>
<VButton
v-close-popper
block
type="default"
@click="handleReloadTheme"
>
{{ $t("core.theme.operations.reload.button") }}
</VButton>
<VButton
v-close-popper
block
type="danger"
@click="handleResetSettingConfig"
>
{{ $t("core.common.buttons.reset") }}
</VButton>
</VSpace>
</div>
<VDropdownItem @click="upgradeModal = true">
{{ $t("core.common.buttons.upgrade") }}
</VDropdownItem>
<VDropdownDivider />
<VDropdownItem type="danger" @click="handleReloadTheme">
{{ $t("core.theme.operations.reload.button") }}
</VDropdownItem>
<VDropdownItem type="danger" @click="handleResetSettingConfig">
{{ $t("core.common.buttons.reset") }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
</div>
</div>
<div class="border-t border-gray-200">

View File

@ -1,14 +1,15 @@
<script lang="ts" setup>
import {
IconGitHub,
VButton,
VSpace,
VTag,
VEntity,
VEntityField,
VStatusDot,
Dialog,
Toast,
VDropdownItem,
VDropdown,
VDropdownDivider,
} from "@halo-dev/components";
import LazyImage from "@/components/image/LazyImage.vue";
import type { Theme } from "@halo-dev/api-client";
@ -192,58 +193,40 @@ const handleUninstall = async (theme: Theme, deleteExtensions?: boolean) => {
v-if="currentUserHasPermission(['system:themes:manage'])"
#dropdownItems
>
<VButton
v-if="!isActivated"
v-close-popper
block
type="secondary"
@click="handleActiveTheme"
>
<VDropdownItem v-if="!isActivated" @click="handleActiveTheme">
{{ $t("core.common.buttons.active") }}
</VButton>
<VButton v-close-popper block type="default" @click="emit('upgrade')">
</VDropdownItem>
<VDropdownItem @click="emit('upgrade')">
{{ $t("core.common.buttons.upgrade") }}
</VButton>
<VButton v-close-popper block type="default" @click="emit('preview')">
</VDropdownItem>
<VDropdownItem @click="emit('preview')">
{{ $t("core.common.buttons.preview") }}
</VButton>
<FloatingDropdown class="w-full" placement="right" :triggers="['click']">
<VButton block type="danger">
</VDropdownItem>
<VDropdownDivider />
<VDropdown placement="right" :triggers="['click']">
<VDropdownItem type="danger">
{{ $t("core.common.buttons.uninstall") }}
</VButton>
</VDropdownItem>
<template #popper>
<div class="w-52 p-2">
<VSpace class="w-full" direction="column">
<VButton
v-close-popper.all
block
type="danger"
@click="handleUninstall(theme)"
>
{{ $t("core.common.buttons.uninstall") }}
</VButton>
<VButton
v-close-popper.all
block
type="danger"
@click="handleUninstall(theme, true)"
>
{{
$t("core.theme.operations.uninstall_and_delete_config.button")
}}
</VButton>
</VSpace>
</div>
<VDropdownItem
v-close-popper.all
type="danger"
@click="handleUninstall(theme)"
>
{{ $t("core.common.buttons.uninstall") }}
</VDropdownItem>
<VDropdownItem
v-close-popper.all
type="danger"
@click="handleUninstall(theme, true)"
>
{{ $t("core.theme.operations.uninstall_and_delete_config.button") }}
</VDropdownItem>
</template>
</FloatingDropdown>
<VButton
v-close-popper
block
type="danger"
@click="handleResetSettingConfig"
>
</VDropdown>
<VDropdownItem type="danger" @click="handleResetSettingConfig">
{{ $t("core.common.buttons.reset") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</template>

View File

@ -1,7 +1,12 @@
<script lang="ts" setup>
import LazyImage from "@/components/image/LazyImage.vue";
import type { Theme } from "@halo-dev/api-client";
import { VEntity, VEntityField, VTag, VButton } from "@halo-dev/components";
import {
VEntity,
VEntityField,
VTag,
VDropdownItem,
} from "@halo-dev/components";
import { toRefs } from "vue";
import { useThemeLifeCycle } from "../../composables/use-theme";
@ -75,23 +80,12 @@ const { isActivated, handleActiveTheme } = useThemeLifeCycle(theme);
</template>
<template #dropdownItems>
<VButton
v-if="!isActivated"
v-close-popper
block
type="secondary"
@click="handleActiveTheme"
>
<VDropdownItem v-if="!isActivated" @click="handleActiveTheme">
{{ $t("core.common.buttons.active") }}
</VButton>
<VButton
v-close-popper
block
type="default"
@click="emit('open-settings')"
>
</VDropdownItem>
<VDropdownItem @click="emit('open-settings')">
{{ $t("core.common.buttons.setting") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</template>

View File

@ -11,6 +11,8 @@ import {
VPagination,
VSpace,
VLoading,
VDropdown,
VDropdownItem,
} from "@halo-dev/components";
import PluginListItem from "./components/PluginListItem.vue";
import PluginUploadModal from "./components/PluginUploadModal.vue";
@ -226,7 +228,7 @@ const { data, isLoading, isFetching, refetch } = useQuery<Plugin[]>({
</div>
<div class="mt-4 flex sm:mt-0">
<VSpace spacing="lg">
<FloatingDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -238,26 +240,19 @@ const { data, isLoading, isFetching, refetch } = useQuery<Plugin[]>({
</span>
</div>
<template #popper>
<div class="w-52 p-4">
<ul class="space-y-1">
<li
v-for="(enabledItem, index) in EnabledItems"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
:class="{
'bg-gray-100':
selectedEnabledItem?.value === enabledItem.value,
}"
@click="handleEnabledItemChange(enabledItem)"
>
<span class="truncate">{{ enabledItem.label }}</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(enabledItem, index) in EnabledItems"
:key="index"
:selected="
enabledItem.value === selectedEnabledItem?.value
"
@click="handleEnabledItemChange(enabledItem)"
>
{{ enabledItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
<FloatingDropdown>
</VDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -269,21 +264,16 @@ const { data, isLoading, isFetching, refetch } = useQuery<Plugin[]>({
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(sortItem, index) in SortItems"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleSortItemChange(sortItem)"
>
<span class="truncate">{{ sortItem.label }}</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(sortItem, index) in SortItems"
:key="index"
:selected="sortItem.value === selectedSortItem?.value"
@click="handleSortItemChange(sortItem)"
>
{{ sortItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
<div class="flex flex-row gap-2">
<div
class="group cursor-pointer rounded p-1 hover:bg-gray-200"

View File

@ -1,6 +1,5 @@
<script lang="ts" setup>
import {
VButton,
VSpace,
VSwitch,
VTag,
@ -10,6 +9,8 @@ import {
VAvatar,
Dialog,
Toast,
VDropdownItem,
VDropdown,
} from "@halo-dev/components";
import PluginUploadModal from "./PluginUploadModal.vue";
import { ref, toRefs } from "vue";
@ -169,49 +170,29 @@ const getFailedMessage = (plugin: Plugin) => {
v-if="currentUserHasPermission(['system:plugins:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block
type="secondary"
@click="upgradeModal = true"
>
<VDropdownItem @click="upgradeModal = true">
{{ $t("core.common.buttons.upgrade") }}
</VButton>
<FloatingDropdown class="w-full" placement="left" :triggers="['click']">
<VButton block type="danger">
</VDropdownItem>
<VDropdown placement="left" :triggers="['click']">
<VDropdownItem type="danger">
{{ $t("core.common.buttons.uninstall") }}
</VButton>
</VDropdownItem>
<template #popper>
<div class="w-52 p-2">
<VSpace class="w-full" direction="column">
<VButton
v-close-popper.all
block
type="danger"
@click="uninstall"
>
{{ $t("core.common.buttons.uninstall") }}
</VButton>
<VButton
v-close-popper.all
block
type="danger"
@click="uninstall(true)"
>
{{ $t("core.plugin.list.actions.uninstall_and_delete_config") }}
</VButton>
</VSpace>
</div>
<VDropdownItem v-close-popper.all type="danger" @click="uninstall">
{{ $t("core.common.buttons.uninstall") }}
</VDropdownItem>
<VDropdownItem
v-close-popper.all
type="danger"
@click="uninstall(true)"
>
{{ $t("core.plugin.list.actions.uninstall_and_delete_config") }}
</VDropdownItem>
</template>
</FloatingDropdown>
<VButton
v-close-popper
block
type="danger"
@click="handleResetSettingConfig"
>
</VDropdown>
<VDropdownItem type="danger" @click="handleResetSettingConfig">
{{ $t("core.common.buttons.reset") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</template>

View File

@ -17,6 +17,7 @@ import {
VEntityField,
VLoading,
Toast,
VDropdownItem,
} from "@halo-dev/components";
import RoleEditingModal from "./components/RoleEditingModal.vue";
@ -249,29 +250,24 @@ const handleDelete = async (role: Role) => {
v-if="currentUserHasPermission(['system:roles:manage'])"
#dropdownItems
>
<VButton
<VDropdownItem
v-if="!isSystemReserved(role)"
v-close-popper
block
type="secondary"
@click="handleOpenEditingModal(role)"
>
{{ $t("core.common.buttons.edit") }}
</VButton>
<VButton
</VDropdownItem>
<VDropdownItem
v-if="!isSystemReserved(role)"
v-close-popper
block
type="danger"
@click="handleDelete(role)"
>
{{ $t("core.common.buttons.delete") }}
</VButton>
<VButton v-close-popper block @click="handleCloneRole(role)">
</VDropdownItem>
<VDropdownItem @click="handleCloneRole(role)">
{{
$t("core.role.operations.create_based_on_this_role.button")
}}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</li>

View File

@ -20,6 +20,9 @@ import {
Toast,
IconRefreshLine,
VEmpty,
VDropdown,
VDropdownItem,
VDropdownDivider,
} from "@halo-dev/components";
import UserEditingModal from "./components/UserEditingModal.vue";
import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
@ -391,7 +394,7 @@ onMounted(() => {
</div>
<div class="mt-4 flex sm:mt-0">
<VSpace spacing="lg">
<FloatingDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -403,28 +406,23 @@ onMounted(() => {
</span>
</div>
<template #popper>
<div class="w-52 p-4">
<ul class="space-y-1">
<li
v-for="(role, index) in roles"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleRoleChange(role)"
>
<span class="truncate">
{{
role.metadata.annotations?.[
rbacAnnotations.DISPLAY_NAME
] || role.metadata.name
}}
</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(role, index) in roles"
:key="index"
:selected="
selectedRole?.metadata.name === role.metadata.name
"
@click="handleRoleChange(role)"
>
{{
role.metadata.annotations?.[
rbacAnnotations.DISPLAY_NAME
] || role.metadata.name
}}
</VDropdownItem>
</template>
</FloatingDropdown>
<FloatingDropdown>
</VDropdown>
<VDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
@ -436,21 +434,16 @@ onMounted(() => {
</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(sortItem, index) in SortItems"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleSortItemChange(sortItem)"
>
<span class="truncate">{{ sortItem.label }}</span>
</li>
</ul>
</div>
<VDropdownItem
v-for="(sortItem, index) in SortItems"
:key="index"
:selected="selectedSortItem?.value === sortItem.value"
@click="handleSortItemChange(sortItem)"
>
{{ sortItem.label }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
<div class="flex flex-row gap-2">
<div
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
@ -575,44 +568,33 @@ onMounted(() => {
v-if="currentUserHasPermission(['system:users:manage'])"
#dropdownItems
>
<VButton
v-close-popper
block
type="secondary"
@click="handleOpenCreateModal(user.user)"
>
<VDropdownItem @click="handleOpenCreateModal(user.user)">
{{ $t("core.user.operations.update_profile.title") }}
</VButton>
<VButton
v-close-popper
block
</VDropdownItem>
<VDropdownItem
@click="handleOpenPasswordChangeModal(user.user)"
>
{{ $t("core.user.operations.change_password.title") }}
</VButton>
<VButton
</VDropdownItem>
<VDropdownItem
v-if="
userStore.currentUser?.metadata.name !==
user.user.metadata.name
"
v-close-popper
block
@click="handleOpenGrantPermissionModal(user.user)"
>
{{ $t("core.user.operations.grant_permission.title") }}
</VButton>
<VButton
</VDropdownItem>
<VDropdownItem
v-if="
userStore.currentUser?.metadata.name !==
user.user.metadata.name
"
v-close-popper
block
type="danger"
@click="handleDelete(user.user)"
>
{{ $t("core.common.buttons.delete") }}
</VButton>
</VDropdownItem>
</template>
</VEntity>
</li>

View File

@ -1,7 +1,13 @@
<script lang="ts" setup>
import BasicLayout from "@/layouts/BasicLayout.vue";
import { apiClient } from "@/utils/api-client";
import { VButton, VSpace, VTabbar, VAvatar } from "@halo-dev/components";
import {
VButton,
VTabbar,
VAvatar,
VDropdown,
VDropdownItem,
} from "@halo-dev/components";
import {
computed,
onMounted,
@ -141,32 +147,19 @@ const handleTabChange = (id: string) => {
currentUserHasPermission(['system:users:manage']) || isCurrentUser
"
>
<FloatingDropdown>
<VDropdown>
<VButton type="default">
{{ $t("core.common.buttons.edit") }}
</VButton>
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<VButton
v-close-popper
block
type="secondary"
@click="editingModal = true"
>
{{ $t("core.user.detail.actions.update_profile.title") }}
</VButton>
<VButton
v-close-popper
block
@click="passwordChangeModal = true"
>
{{ $t("core.user.detail.actions.change_password.title") }}
</VButton>
</VSpace>
</div>
<VDropdownItem @click="editingModal = true">
{{ $t("core.user.detail.actions.update_profile.title") }}
</VDropdownItem>
<VDropdownItem @click="passwordChangeModal = true">
{{ $t("core.user.detail.actions.change_password.title") }}
</VDropdownItem>
</template>
</FloatingDropdown>
</VDropdown>
</div>
</div>
</div>

View File

@ -1,6 +1,5 @@
import type { App } from "vue";
import { Dropdown, Menu, Tooltip, VClosePopper, VTooltip } from "floating-vue";
import "floating-vue/dist/style.css";
import { VClosePopper, VTooltip } from "@halo-dev/components";
// @ts-ignore
import VueGridLayout from "vue-grid-layout";
import { defaultConfig, plugin as FormKit } from "@formkit/vue";
@ -17,7 +16,4 @@ export function setupComponents(app: App) {
app.directive("tooltip", VTooltip);
app.directive("close-popper", VClosePopper);
app.component("FloatingDropdown", Dropdown);
app.component("FloatingTooltip", Tooltip);
app.component("FloatingMenu", Menu);
}