feat: Add dropdown component (#10358)
This PR adds dropdown primitives to help compose custom dropdowns across the app. The following the sample usage --------- Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div class="absolute">
|
||||
<ul
|
||||
class="text-sm bg-n-solid-1 border border-n-weak rounded-xl shadow-sm py-2 n-dropdown-body gap-2 grid list-none px-2 reset-base"
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
import { useToggle } from '@vueuse/core';
|
||||
import { provideDropdownContext } from './provider.js';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const [isOpen, toggle] = useToggle(false);
|
||||
|
||||
const closeMenu = () => {
|
||||
if (isOpen.value) {
|
||||
emit('close');
|
||||
toggle(false);
|
||||
}
|
||||
};
|
||||
|
||||
provideDropdownContext({
|
||||
isOpen,
|
||||
toggle,
|
||||
closeMenu,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative z-20 space-y-2">
|
||||
<slot name="trigger" :is-open :toggle="() => toggle()" />
|
||||
<div v-if="isOpen" v-on-clickaway="closeMenu" class="absolute">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import { useDropdownContext } from './provider.js';
|
||||
|
||||
const props = defineProps({
|
||||
label: { type: String, default: '' },
|
||||
icon: { type: [String, Object, Function], default: '' },
|
||||
link: { type: String, default: '' },
|
||||
click: { type: Function, default: null },
|
||||
preserveOpen: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const { closeMenu } = useDropdownContext();
|
||||
|
||||
const componentIs = computed(() => {
|
||||
if (props.link) return 'router-link';
|
||||
if (props.click) return 'button';
|
||||
|
||||
return 'div';
|
||||
});
|
||||
|
||||
const triggerClick = () => {
|
||||
if (props.click) {
|
||||
props.click();
|
||||
if (!props.preserveOpen) closeMenu();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li class="n-dropdown-item">
|
||||
<component
|
||||
:is="componentIs"
|
||||
v-bind="$attrs"
|
||||
class="flex text-left rtl:text-right items-center p-2 reset-base text-sm text-n-slate-12 w-full border-0"
|
||||
:class="{
|
||||
'hover:bg-n-alpha-1 rounded-lg w-full gap-3': !$slots.default,
|
||||
}"
|
||||
:href="props.link || null"
|
||||
@click="triggerClick"
|
||||
>
|
||||
<slot>
|
||||
<slot name="icon">
|
||||
<Icon v-if="icon" class="size-4 text-n-slate-11" :icon="icon" />
|
||||
</slot>
|
||||
<slot name="label">{{ label }}</slot>
|
||||
</slot>
|
||||
</component>
|
||||
</li>
|
||||
</template>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="-mx-2 n-dropdown-section">
|
||||
<div
|
||||
v-if="title"
|
||||
class="px-4 mb-3 mt-1 leading-4 font-medium tracking-[0.2px] text-n-slate-10 text-xs"
|
||||
>
|
||||
{{ title }}
|
||||
</div>
|
||||
<ul class="gap-2 grid reset-base list-none px-2">
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div class="h-0 border-b border-n-strong -mx-2" />
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
import DropdownBody from './DropdownBody.vue';
|
||||
import DropdownContainer from './DropdownContainer.vue';
|
||||
import DropdownItem from './DropdownItem.vue';
|
||||
import DropdownSection from './DropdownSection.vue';
|
||||
import DropdownSeparator from './DropdownSeparator.vue';
|
||||
|
||||
export {
|
||||
DropdownBody,
|
||||
DropdownContainer,
|
||||
DropdownItem,
|
||||
DropdownSection,
|
||||
DropdownSeparator,
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import { inject, provide } from 'vue';
|
||||
|
||||
const DropdownControl = Symbol('DropdownControl');
|
||||
|
||||
export function useDropdownContext() {
|
||||
const context = inject(DropdownControl, null);
|
||||
|
||||
if (context === null) {
|
||||
throw new Error(
|
||||
`Component is missing a parent <DropdownContainer /> component.`
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export function provideDropdownContext(context) {
|
||||
provide(DropdownControl, context);
|
||||
}
|
||||
Reference in New Issue
Block a user