diff --git a/n-link-core/components/Popover.spec.ts b/n-link-core/components/Popover.spec.ts
new file mode 100644
index 0000000..0ad99b5
--- /dev/null
+++ b/n-link-core/components/Popover.spec.ts
@@ -0,0 +1,52 @@
+import { describe, it, expect } from 'vitest';
+import { mount } from '@vue/test-utils';
+import Popover from './Popover.vue';
+
+function make() {
+ return mount(Popover, {
+ attachTo: document.body,
+ slots: {
+ trigger: '',
+ default: '
panel body
',
+ },
+ });
+}
+
+describe('Popover', () => {
+ it('hides the panel initially', () => {
+ expect(make().find('[data-testid="panel"]').exists()).toBe(false);
+ });
+
+ it('opens when the trigger is clicked', async () => {
+ const wrapper = make();
+ await wrapper.get('.trg').trigger('click');
+ expect(wrapper.find('[data-testid="panel"]').exists()).toBe(true);
+ expect(wrapper.emitted('update:visible')?.at(-1)).toEqual([true]);
+ });
+
+ it('closes on Escape', async () => {
+ const wrapper = make();
+ await wrapper.get('.trg').trigger('click');
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('[data-testid="panel"]').exists()).toBe(false);
+ });
+
+ it('closes on an outside mousedown', async () => {
+ const wrapper = make();
+ await wrapper.get('.trg').trigger('click');
+ document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('[data-testid="panel"]').exists()).toBe(false);
+ });
+
+ it('applies a width style to the panel', async () => {
+ const wrapper = mount(Popover, {
+ attachTo: document.body,
+ props: { width: 200 },
+ slots: { trigger: '', default: 'b
' },
+ });
+ await wrapper.get('.trg').trigger('click');
+ expect(wrapper.get('[data-testid="panel"]').attributes('style')).toContain('width: 200px');
+ });
+});
diff --git a/n-link-core/components/Popover.vue b/n-link-core/components/Popover.vue
new file mode 100644
index 0000000..7468d39
--- /dev/null
+++ b/n-link-core/components/Popover.vue
@@ -0,0 +1,64 @@
+
+
+
+
+