Security News
Opengrep Emerges as Open Source Alternative Amid Semgrep Licensing Controversy
Opengrep forks Semgrep to preserve open source SAST in response to controversial licensing changes.
A tooltip and popover plugin for Vue.js.
Vue.js 3.2+ required.
Tooltip
components can display a simple content
message.
<template>
<Tooptip content="This is a very important message">
<BasicButton>Hover Me</BasicButton>
</Tooptip>
</template>
Tooltips accept multiple props that override the default tooltip behavior.
<template>
<Tooltip
content="This is a very important message"
placement="bottom-start"
action="click"
>
<BaseButton>Click Me</BaseButton>
</Tooltip>
</template>
Popovers are used to display dynamic, interactive content over your UI. They have different styling and behavior than tooltips, but can be further customized using props.
Popovers require a trigger element and custom content to display.
Triggers are defined by either a PopoverTrigger
component or a v-popover
directive.
The difference between these options is that while a PopoverTrigger
component may be linked with an external PopoverContent
component, it also provides a content
slot to conveniently display popover content inline.
Alternatively, using a directive requires the use of an external PopoverContent
component to display its popover content.
Consider the use of a single trigger that is colocated with its popover content.
A PopoverTrigger
may define its content in a slot, while the directive requires use of a separate PopoverContent
component, linked by a common name
.
<!--Component trigger-->
<template>
<PopoverTrigger>
<BaseButton>
<IconSettings class="mr-2 w-4 h-4" />Settings
</BaseButton>
<template #content>
<div class="rounded-md space-y-1">
<BaseButton transparent>Account Settings</BaseButton>
<BaseButton transparent>Support</BaseButton>
<BaseButton transparent>License</BaseButton>
<BaseButton transparent>Sign out</BaseButton>
</div>
</template>
</PopoverTrigger>
</template>
<!--Directive trigger-->
<template>
<BaseButton v-popover="{ name: 'menu' }">
<IconSettings class="mr-2 w-4 h-4" />Settings
</BaseButton>
<PopoverContent name="menu">
<div class="rounded-md space-y-1">
<BaseButton transparent>Account Settings</BaseButton>
<BaseButton transparent>Support</BaseButton>
<BaseButton transparent>License</BaseButton>
<BaseButton transparent>Sign out</BaseButton>
</div>
</PopoverContent>
</template>
The extra complexity introduced by having to use a separate component linked by a common name
likely makes the directive a less attractive option for this simple use case.
When the popover trigger and content are not colocated, or a single content section needs be shared between multiple triggers, a PopoverContent
component is required with a name
prop.
The same name
prop must be set on the PopoverTarget
components or v-popover
directives to link the triggers and content.
Common popover options may be set on PopoverContent
via props, but triggers may override these options with their own props.
<!--Component trigger-->
<script setup>
import { ref } from 'vue';
const menu = ref([
{ label: 'Products', opts: { data: 'products' } },
{ label: 'Resources', opts: { data: 'resources' } },
{ label: 'Pricing', opts: { data: 'pricing', placement: 'bottom-end' } },
{ label: 'Settings', opts: { data: 'settings', placement: 'bottom-end' } },
]);
</script>
<template>
<div class="flex gap-2">
<PopoverTrigger
v-for="{ label, opts } in menu"
:key="label"
v-bind="{ ...opts, name: 'shared_popover' }"
>
<BaseButton transparent>
{{ label }}
</BaseButton>
</PopoverTrigger>
</div>
<PopoverContent name="shared_popover" v-slot="{ data }">
<div class="space-y-1">
<template v-if="data === 'products'">
<BaseButton transparent>Product A</BaseButton>
<BaseButton transparent>Product B</BaseButton>
<BaseButton transparent>Product C</BaseButton>
</template>
<template v-else-if="data === 'resources'">
<BaseButton transparent>Resource A</BaseButton>
<BaseButton transparent>Resource B</BaseButton>
<BaseButton transparent>Resource C</BaseButton>
<BaseButton transparent>Resource D</BaseButton>
</template>
<template v-else-if="data === 'pricing'">
<BaseButton transparent>Pricing A</BaseButton>
<BaseButton transparent>Pricing B</BaseButton>
</template>
<template v-else-if="data === 'settings'">
<BaseButton transparent>Account Settings</BaseButton>
<BaseButton transparent>Support</BaseButton>
<BaseButton transparent>License</BaseButton>
<BaseButton transparent>Sign out</BaseButton>
</template>
</div>
</PopoverContent>
</template>
Notice how we assign specific data
for each trigger. This data
can be any value, and is passed through to the PopoverContent
slot when triggered in order to display separate user interfaces, depending on which trigger is active.
Use the open
slot prop to detect if the popover is open for a particular PopoverTrigger
.
<template>
<PopoverTrigger :transitions="['fade', 'slide']">
<template #default="{ open }">
<BaseButton transparent>
Settings
<IconChevronDown v-if="!open" class="ml-2 w-3.5 h-3.5" />
<IconChevronUp v-else class="ml-2 w-3.5 h-3.5" />
</BaseButton>
</template>
<template #content>
<div class="rounded-md space-y-1">
<BaseButton transparent>Account Settings</BaseButton>
<BaseButton transparent>Support</BaseButton>
<BaseButton transparent>License</BaseButton>
<BaseButton transparent>Sign out</BaseButton>
</div>
</template>
</PopoverTrigger>
</template>
By default, when PopoverContent
is shared between multiple triggers, the content pane will transition between the trigger elements as the trigger actions occur.
To disable this behavior, the move
option can be omitted from the transitions
array.
When a popover is shown with the click
action, focus is placed inside it, as it is a focus trap. A user cannot tab outside of the popover until it is closed by an outside click or the escape key. If the popover has focus when it is closed, focus is placed back on the trigger element.
There are multiple ways to register for popover events.
Generally, there are 3 types of events. Each event includes a readonly copy of the popover's state in the payload.
Show: Sent when a popover is opened.
Hide: Sent when a popover is hidden.
Update: Sent when a popover's state is updated. Included in the payload is an updates
object with the updated state properties as keys mapped to their old and new values in an array.
// Update event payload
{
// Current popover state
direction: 'top',
alignment: 'right',
// ...
updates: {
direction: ['bottom', 'top'],
alignment: ['left', 'right'],
// ...
}
}
Events may be registered directly on components or globally via helper methods included with the plugin.
Event listeners for all event types can be directly set on PopoverTrigger
and PopoverContent
components.
If a PopoverContent
component is shared across multiple triggers, and it is transitioning from one trigger to another, a separate hide
event is emitted for the old trigger and a show
event is emitted for the new trigger.
If you don't have direct access to PopoverTrigger
or PopoverContent
components, events can be registered globally on the document via popovershow
, popoverhide
and popoverupdate
.
onPopoverEvent()
Import the onPopoverEvent
helper to easily register handlers for these events. This helper returns a cleanup function that can be called at a later time.
import { onPopoverEvent } from 'v-popover';
const off = onPopoverEvent('popovershow', (e) => {
console.log('Popover displayed for', e.detail.name);
});
// Cleanup later
off();
Since this callback will get called for every popover in your app, you can check for specific popovers within the callback, or pass an optional filter object as the third parameter.
import { onPopoverEvent } from 'v-popover';
// Method 1: Filter out events within callback
onPopoverEvent('popovershow', (e) => {
if (e.detail.name === 'my_popover') {
console.log('Popover displayed for', e.detail.name);
}
});
// Method 2: Filter out events with optional filter
onPopoverEvent('popovershow', (e) => {
console.log('Popover displayed for', e.detail.name);
}, {
name: 'my_popover',
// Other specific conditions
id: 'my_popover_trigger',
});
// Use `updates` to only get callbacks for specific state updates
onPopoverEvent('popoverupdate', (e) => {
console.log('Direction changed for popover', e.detail.name);
}, {
name: 'my_popover',
id: 'my_popover_trigger',
// Only updates to these state keys will trigger callback
updates: ['direction'],
})
usePopoverEvent()
Within components, you can call usePopoverEvent
to automatically register the callback in onMounted
and unregister the callback in onUnmounted
.
Otherwise, this function behaves exactly the same as onPopoverEvent()
.
<script setup>
import { usePopoverEvent } from 'v-popover';
usePopoverEvent('popoverupdate', (e) => {
console.log('Direction changed for popover', e.detail.name);
}, {
name: 'my_popover',
id: 'my_popover_trigger',
updates: ['direction'],
})
</script>
VPopover provides customized styling support via the theme
and contentClass
properties.
theme
Basic light/dark mode styling can be optionally applied by setting the theme
property on components or the directives.
For example, the Tooltip
component uses the dark theme under the hood, but can be reset to the light theme by manually.
<template>
<div class="flex gap-2">
<Tooltip content="I am dark">
<BaseButton>Dark Tooltip</BaseButton>
</Tooltip>
<Tooltip theme="light" content="I am light">
<BaseButton>Light Tooltip</BaseButton>
</Tooltip>
</div>
</template>
A theme can be cleared by setting theme
to a falsey value. This will clear all of the default styling for the popover content.
<template>
<Tooltip content="I have no styling applied" theme="">
<BaseButton>Dark Tooltip</BaseButton>
</Tooltip>
</template>
Since the theme
is now cleared, custom styling can be easily applied using contentClass
.
contentClass
The contentClass
property can be used to apply a custom class to the popover content window.
<template>
<Tooltip
content="WARNING: This is a dangerous operation"
content-class="font-bold bg-red-100 text-red-600 px-2 py-1 border border-red-300 rounded-lg"
:arrow-size="14"
theme=""
>
<BaseButton class="bg-red-500 hover:bg-red-700">Dangerous Action</BaseButton>
</Tooltip>
</template>
Vue.js 3.2+ is required
// npm
npm install v-popover
// yarn
yarn add v-popover
As of v3.0.0-alpha.7
, all installation methods require manual import of component styles. This is due to Vite build restrictions in libary mode.
import 'v-popover/style.css';
// main.js
import VPopover from 'v-popover';
import 'v-popover/style.css';
// Use plugin with optional defaults
app.use(VPopover, {})
<!--MyComponent.vue-->
<template>
<VPopover />
</template>
// main.js
import { PopoverTrigger, PopoverContent } from 'v-popover';
import 'v-popover/style.css';
// Use plugin defaults (optional)
app.use(setupCalendar, {})
// Use the components
app.component('PopoverTrigger', PopoverTrigger)
app.component('PopoverContent', PopoverContent)
<!--MyComponent.vue-->
<template>
<PopoverTrigger />
<PopoverContent />
</template>
<!--MyComponent.vue-->
<template>
<PopoverTrigger>
<button type="button">Show content</button>
<PopoverTrigger>
<PopoverContent>
Custom content here
</PopoverContent>
<DatePicker v-model="date">
</template>
<script setup>
import { PopoverTrigger, PopoverContent } from 'v-popover';
import 'v-popover/style.css';
</script>
FAQs
A popover plugin for Vue.js.
We found that v-popover demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Opengrep forks Semgrep to preserve open source SAST in response to controversial licensing changes.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
Security News
cURL and Go security teams are publicly rejecting CVSS as flawed for assessing vulnerabilities and are calling for more accurate, context-aware approaches.