ActionDropdown Component
The ActionDropdown
is a reusable Vue.js component built using PrimeVue's Button
and Menu
components. It provides a dropdown menu for actions, allowing developers to define customizable menu items with labels, icons, commands, and separators.
Features
- Customizable Button: Configure label, icon, severity (e.g., primary, secondary), size, and more.
- Dynamic Menu Items: Define menu actions with labels, icons, commands, and styling.
-
Event Emission: Emits an
action
event when a menu item is clicked. - Flexible Alignment: Align the dropdown menu to the left or right.
- Accessibility: Supports disabling the button or individual menu items.
- Styling Support: Tailwind CSS and scoped styles for customization.
Example Usage
Here’s how you can use the ActionDropdown
component in your Vue application:
<template>
<div>
<ActionDropdown
:actions="getRowActions"
button-label="Actions"
button-icon="pi pi-ellipsis-v"
button-severity="secondary"
size="small"
text-only
@action="handleAction"
/>
</div>
</template>
<script setup>
import { ref } from "vue";
import ActionDropdown from "@/components/buttons/ActionDropdown.vue";
const getRowActions = [
{
label: "View Details",
icon: "pi pi-eye",
command: () => viewDetails(),
},
{
label: "Edit",
icon: "pi pi-pencil",
command: () => editItem(),
},
{ separator: true },
{
label: "Delete",
icon: "pi pi-trash",
command: () => confirmDelete(),
class: "danger-item",
},
];
const handleAction = (action) => {
console.log("Action performed:", action.label);
};
const viewDetails = () => {
console.log("Viewing details...");
};
const editItem = () => {
console.log("Editing item...");
};
const confirmDelete = () => {
console.log("Confirming delete...");
};
</script>
Props
Prop | Type | Default | Description |
---|---|---|---|
actions |
Array |
[] |
List of menu actions. Each action can have label , icon , command , class , and separator . |
buttonLabel |
String |
"Actions" |
Label text for the button. |
buttonIcon |
String |
"pi pi-ellipsis-v" |
Icon class for the button. |
buttonSeverity |
String |
"secondary" |
Button severity: primary , secondary , success , info , warning , danger , help . |
size |
String |
"medium" |
Button size: small , medium , large . |
textOnly |
Boolean |
false |
If true , the button will be styled as a text button. |
disabled |
Boolean |
false |
Disables the button. |
menuClass |
String |
"" |
Additional CSS classes for the dropdown menu. |
appendTo |
String |
"body" |
Determines where to append the dropdown menu. |
menuAlignment |
String |
"left" |
Menu alignment: left or right . |
Events
Event | Payload | Description |
---|---|---|
action |
Object |
Fired when a menu item is clicked. Payload contains the action's properties. |
Styling
The ActionDropdown
component uses Tailwind CSS for styling. Below are some key customizations:
Button Styling
-
.p-button-sm
: Small size -
.p-button-lg
: Large size -
.p-button-text
: Text-only button
Menu Styling
-
.p-menuitem
: Default menu item styling. -
.danger-item
: Styling for actions that are marked as "danger" (e.g., delete).
Alignment
-
.menu-right
: Aligns the dropdown to the right.
Size Variants
-
.small
: Small menu items. -
.large
: Large menu items.
Notes
-
Dependencies:
- This component relies on PrimeVue's
Button
andMenu
components. Make sure to install and configure PrimeVue in your project.
- This component relies on PrimeVue's
-
Tailwind CSS:
- The styles use Tailwind CSS utilities. Ensure Tailwind CSS is configured in your project.
-
Validation:
- Action items are validated to ensure each item has a valid configuration (e.g., label and command are required unless it's a separator).
Installation
To use the ActionDropdown
component, copy the file to your project and import it as needed.
Install PrimeVue and Tailwind CSS if not already installed:
npm install primevue @tailwindcss/postcss7-compat
Configure PrimeVue in your main file:
import { createApp } from "vue";
import PrimeVue from "primevue/config";
import App from "./App.vue";
import "./tailwind.css"; // Include Tailwind CSS
const app = createApp(App);
app.use(PrimeVue);
app.mount("#app");
<template>
<div class="action-dropdown-container">
<Button
:label="buttonLabel"
:icon="buttonIcon"
:severity="buttonSeverity"
:class="['action-button', sizeClass, { 'p-button-text': textOnly }]"
@click="toggleDropdown"
:disabled="disabled"
/>
<Menu
ref="menu"
:model="menuItems"
:popup="true"
:class="menuClass"
:appendTo="appendTo"
/>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
import Button from "primevue/button";
import Menu from "primevue/menu";
const props = defineProps({
// Action items configuration
actions: {
type: Array,
default: () => [],
validator: (items) => {
return items.every((item) => {
// Either has command/to/url, or is a separator
return (
item.separator ||
(item.label && (item.command || item.to || item.url))
);
});
},
},
// Button appearance
buttonLabel: {
type: String,
default: "Actions",
},
buttonIcon: {
type: String,
default: "pi pi-ellipsis-v",
},
buttonSeverity: {
type: String,
default: "secondary",
validator: (value) =>
[
"primary",
"secondary",
"success",
"info",
"warning",
"danger",
"help",
].includes(value),
},
textOnly: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
// Size and layout
size: {
type: String,
default: "medium",
validator: (value) => ["small", "medium", "large"].includes(value),
},
fullWidth: {
type: Boolean,
default: false,
},
// Menu configuration
menuClass: {
type: String,
default: "",
},
appendTo: {
type: String,
default: "body",
},
menuAlignment: {
type: String,
default: "left",
validator: (value) => ["left", "right"].includes(value),
},
});
const emit = defineEmits(["action"]);
const menu = ref(null);
// Create stable menu items without reactive dependencies
const menuItems = computed(() => {
return props.actions.map((action) => {
if (action.separator) {
return { separator: true };
}
return {
label: action.label,
icon: action.icon,
class: action.class,
disabled: action.disabled,
command: () => {
emit("action", action);
if (action.command) {
action.command();
}
},
};
});
});
const sizeClass = computed(() => {
return {
small: "p-button-sm",
medium: "",
large: "p-button-lg",
}[props.size];
});
const toggleDropdown = (event) => {
if (props.disabled) return;
menu.value.toggle(event);
};
</script>
<style scoped>
.action-dropdown-container {
@apply relative inline-flex;
}
.action-button {
@apply flex items-center justify-center gap-2;
&.p-button-text {
@apply shadow-none;
}
}
/* Full width variant */
.full-width {
@apply w-full;
.action-button {
@apply w-full;
}
}
</style>
<style>
/* Menu styling - using global style to override PrimeVue defaults */
.action-dropdown-container .p-menu {
@apply min-w-[10rem] shadow-lg rounded-md border border-gray-200 mt-1 z-50;
.p-menuitem {
@apply hover:bg-gray-50;
&.p-disabled {
@apply opacity-50 cursor-not-allowed;
}
}
.p-menuitem-link {
@apply px-4 py-2 text-sm text-gray-700 hover:text-gray-900 hover:bg-gray-50;
.p-menuitem-icon {
@apply text-gray-500 mr-2;
}
.p-menuitem-text {
@apply flex-grow;
}
}
.p-menu-separator {
@apply border-t border-gray-200 my-1;
}
/* Danger items styling */
.danger-item {
@apply text-red-600 hover:bg-red-50;
.p-menuitem-icon {
@apply text-red-500;
}
}
}
/* Right-aligned menu */
.menu-right .p-menu {
@apply right-0 left-auto;
}
/* Size variants */
.action-dropdown-container.small .p-menu {
.p-menuitem-link {
@apply px-3 py-1.5 text-xs;
}
}
.action-dropdown-container.large .p-menu {
.p-menuitem-link {
@apply px-5 py-3 text-base;
}
}
</style>
Top comments (0)