mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-30 02:32:29 +00:00
This PR has the initial version of the new sidebar targeted for the next major redesign of the app. This PR includes the following changes - Components in the `layouts-next` and `base-next` directories in `dashboard/components` - Two generic components `Avatar` and `Icon` - `SidebarGroup` component to manage expandable sidebar groups with nested navigation items. This includes handling active states, transitions, and permissions. - `SidebarGroupHeader` component to display the header of each navigation group with optional icons and active state indication. - `SidebarGroupLeaf` component for individual navigation items within a group, supporting icons and active state. - `SidebarGroupSeparator` component to visually separate nested navigation items. (They look a lot like header) - `SidebarGroupEmptyLeaf` component to render empty state of any navigation groups. ---- Co-authored-by: Pranav <pranav@chatwoot.com> Co-authored-by: Pranav <pranavrajs@gmail.com>
93 lines
2.3 KiB
Vue
93 lines
2.3 KiB
Vue
<script setup>
|
|
import { isVNode, computed } from 'vue';
|
|
import Icon from 'next/icon/Icon.vue';
|
|
import Policy from 'dashboard/components/policy.vue';
|
|
import { useSidebarContext } from './provider';
|
|
|
|
const props = defineProps({
|
|
label: { type: String, required: true },
|
|
to: { type: [String, Object], required: true },
|
|
icon: { type: [String, Object], default: null },
|
|
active: { type: Boolean, default: false },
|
|
component: { type: Function, default: null },
|
|
});
|
|
|
|
const { resolvePermissions, resolveFeatureFlag } = useSidebarContext();
|
|
|
|
const shouldRenderComponent = computed(() => {
|
|
return typeof props.component === 'function' || isVNode(props.component);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<Policy
|
|
:permissions="resolvePermissions(to)"
|
|
:feature-flag="resolveFeatureFlag(to)"
|
|
as="li"
|
|
class="py-0.5 ltr:pl-3 rtl:pr-3 rtl:mr-3 ltr:ml-3 relative text-n-slate-11 child-item before:bg-n-slate-4 after:bg-transparent after:border-n-slate-4 before:left-0 rtl:before:right-0"
|
|
>
|
|
<component
|
|
:is="to ? 'router-link' : 'div'"
|
|
:to="to"
|
|
:title="label"
|
|
class="flex h-8 items-center gap-2 px-2 py-1 rounded-lg max-w-[151px] hover:bg-gradient-to-r from-transparent via-n-slate-3/70 to-n-slate-3/70 group"
|
|
:class="{
|
|
'text-n-blue bg-n-alpha-2 active': active,
|
|
}"
|
|
>
|
|
<component
|
|
:is="component"
|
|
v-if="shouldRenderComponent"
|
|
:label
|
|
:icon
|
|
:active
|
|
/>
|
|
<template v-else>
|
|
<Icon v-if="icon" :icon="icon" class="size-4 inline-block" />
|
|
<div class="flex-1 truncate min-w-0">{{ label }}</div>
|
|
</template>
|
|
</component>
|
|
</Policy>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.child-item::before {
|
|
content: '';
|
|
position: absolute;
|
|
width: 0.125rem;
|
|
/* 0.5px */
|
|
height: 100%;
|
|
}
|
|
|
|
.child-item:first-child::before {
|
|
border-radius: 4px 4px 0 0;
|
|
}
|
|
|
|
.last-child-item::before {
|
|
height: 20%;
|
|
}
|
|
|
|
.last-child-item::after {
|
|
content: '';
|
|
position: absolute;
|
|
width: 10px;
|
|
height: 12px;
|
|
bottom: calc(50% - 2px);
|
|
border-bottom-width: 0.125rem;
|
|
border-left-width: 0.125rem;
|
|
border-right-width: 0px;
|
|
border-top-width: 0px;
|
|
border-radius: 0 0 0 4px;
|
|
left: 0;
|
|
}
|
|
|
|
.app-rtl--wrapper .last-child-item::after {
|
|
right: 0;
|
|
border-bottom-width: 0.125rem;
|
|
border-right-width: 0.125rem;
|
|
border-left-width: 0px;
|
|
border-top-width: 0px;
|
|
border-radius: 0 0 4px 0px;
|
|
}
|
|
</style>
|