## Description * Added the ability to sort for 4 sub-sections under conversations folders, teams, channels and labels. * the sort options are basically created at, alphabetical and unread counts along with both directions. * for folders we don't have an unread count, so we sort it by only created and alphabetical. * all the sort preferences are stored on the frontend - easiest implementation for now. Fixes # CW-7193 ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Tested this locally by visually verifying the changes. Also ran the newly added tests for component level changes. Here is the screenshot of the changes: Added the sort option to sub sections in the conversation sidebar: <img width="282" height="848" alt="Screenshot 2026-06-02 at 1 58 12 AM" src="https://github.com/user-attachments/assets/4a7c6061-86e3-438a-92ae-ee643a0128b6" /> The sort options looks like this: <img width="783" height="698" alt="Screenshot 2026-06-02 at 1 58 49 AM" src="https://github.com/user-attachments/assets/a15bb0a7-b810-4423-a88c-fbd84d0476c0" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com>
183 lines
5.5 KiB
Vue
183 lines
5.5 KiB
Vue
<script setup>
|
|
import { computed, ref, watch } from 'vue';
|
|
import { useEventListener } from '@vueuse/core';
|
|
import { useMapGetter } from 'dashboard/composables/store';
|
|
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
|
import { LocalStorage } from 'shared/helpers/localStorage';
|
|
import Icon from 'next/icon/Icon.vue';
|
|
import SidebarGroupLeaf from './SidebarGroupLeaf.vue';
|
|
import SidebarGroupSeparator from './SidebarGroupSeparator.vue';
|
|
|
|
import { useSidebarContext } from './provider';
|
|
|
|
const props = defineProps({
|
|
name: { type: String, required: true },
|
|
isExpanded: { type: Boolean, default: false },
|
|
label: { type: String, required: true },
|
|
icon: { type: [Object, String], required: true },
|
|
children: { type: Array, default: undefined },
|
|
activeChild: { type: Object, default: undefined },
|
|
sortOptions: { type: Array, default: () => [] },
|
|
activeSort: { type: String, default: '' },
|
|
collapsible: { type: Boolean, default: false },
|
|
showTreeLine: { type: Boolean, default: false },
|
|
endTreeLine: { type: Boolean, default: false },
|
|
});
|
|
|
|
const emit = defineEmits(['update-sort']);
|
|
|
|
const { isAllowed } = useSidebarContext();
|
|
const scrollableContainer = ref(null);
|
|
const accountId = useMapGetter('getCurrentAccountId');
|
|
|
|
const minimizedSectionsKey = LOCAL_STORAGE_KEYS.SIDEBAR_MINIMIZED_SECTIONS;
|
|
|
|
const getMinimizedSections = () => {
|
|
const minimizedSections = LocalStorage.get(minimizedSectionsKey);
|
|
return minimizedSections &&
|
|
typeof minimizedSections === 'object' &&
|
|
!Array.isArray(minimizedSections)
|
|
? minimizedSections
|
|
: {};
|
|
};
|
|
|
|
const minimizedSections = ref(getMinimizedSections());
|
|
const storageKey = computed(() =>
|
|
accountId.value ? `${accountId.value}:${props.name}` : props.name
|
|
);
|
|
const isSubGroupExpanded = computed(
|
|
() => !props.collapsible || !minimizedSections.value[storageKey.value]
|
|
);
|
|
const hasActiveChild = computed(() =>
|
|
props.children.some(child => child.name === props.activeChild?.name)
|
|
);
|
|
|
|
const accessibleItems = computed(() =>
|
|
props.children.filter(child => {
|
|
return child.to && isAllowed(child.to);
|
|
})
|
|
);
|
|
|
|
const hasAccessibleItems = computed(() => {
|
|
return accessibleItems.value.length > 0;
|
|
});
|
|
|
|
const isScrollable = computed(() => {
|
|
return (
|
|
props.isExpanded &&
|
|
isSubGroupExpanded.value &&
|
|
accessibleItems.value.length > 7
|
|
);
|
|
});
|
|
|
|
const scrollEnd = ref(false);
|
|
|
|
const CHILDREN_TRUNK =
|
|
"before:content-[''] before:absolute before:top-0 before:bottom-0 before:w-0.5 before:bg-n-slate-4 before:start-[-0.5rem]";
|
|
|
|
const hideLeafTreeLine = computed(
|
|
() => props.showTreeLine && !props.isExpanded
|
|
);
|
|
|
|
const toggleSubGroup = () => {
|
|
if (!props.collapsible) return;
|
|
|
|
if (isSubGroupExpanded.value) {
|
|
LocalStorage.updateJsonStore(minimizedSectionsKey, storageKey.value, true);
|
|
} else {
|
|
LocalStorage.deleteFromJsonStore(minimizedSectionsKey, storageKey.value);
|
|
}
|
|
|
|
minimizedSections.value = getMinimizedSections();
|
|
};
|
|
|
|
const expandSubGroupOnActiveChild = () => {
|
|
if (!props.collapsible || !hasActiveChild.value || isSubGroupExpanded.value) {
|
|
return;
|
|
}
|
|
|
|
LocalStorage.deleteFromJsonStore(minimizedSectionsKey, storageKey.value);
|
|
minimizedSections.value = getMinimizedSections();
|
|
};
|
|
|
|
const shouldShowItem = child => {
|
|
return (
|
|
isSubGroupExpanded.value &&
|
|
(props.isExpanded || props.activeChild?.name === child.name)
|
|
);
|
|
};
|
|
|
|
// set scrollEnd to true when the scroll reaches the end
|
|
useEventListener(scrollableContainer, 'scroll', () => {
|
|
const { scrollHeight, scrollTop, clientHeight } = scrollableContainer.value;
|
|
scrollEnd.value = scrollHeight - scrollTop === clientHeight;
|
|
});
|
|
|
|
useEventListener(window, 'storage', event => {
|
|
if (event.key === minimizedSectionsKey) {
|
|
minimizedSections.value = getMinimizedSections();
|
|
}
|
|
});
|
|
|
|
watch([hasActiveChild, storageKey], expandSubGroupOnActiveChild, {
|
|
immediate: true,
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<li class="group/sidebar-section relative flex flex-col list-none min-w-0">
|
|
<template v-if="hasAccessibleItems">
|
|
<SidebarGroupSeparator
|
|
v-show="isExpanded"
|
|
:label
|
|
:icon
|
|
:collapsible
|
|
:is-expanded="isSubGroupExpanded"
|
|
:show-tree-line="showTreeLine"
|
|
:end-tree-line="endTreeLine"
|
|
:sort-options="sortOptions"
|
|
:active-sort="activeSort"
|
|
class="my-1"
|
|
@toggle="toggleSubGroup"
|
|
@update-sort="sortBy => emit('update-sort', sortBy)"
|
|
/>
|
|
<ul
|
|
v-if="children.length"
|
|
class="m-0 list-none reset-base relative group min-w-0"
|
|
:class="[
|
|
{ 'ms-5': collapsible },
|
|
showTreeLine && !endTreeLine && CHILDREN_TRUNK,
|
|
]"
|
|
>
|
|
<div
|
|
ref="scrollableContainer"
|
|
class="min-w-0"
|
|
:class="{
|
|
'max-h-60 overflow-y-scroll no-scrollbar': isScrollable,
|
|
}"
|
|
>
|
|
<SidebarGroupLeaf
|
|
v-for="child in children"
|
|
v-show="shouldShowItem(child)"
|
|
v-bind="child"
|
|
:key="child.name"
|
|
:active="activeChild?.name === child.name"
|
|
:hide-tree-line="hideLeafTreeLine"
|
|
thin-tree-line
|
|
/>
|
|
</div>
|
|
<div
|
|
v-if="isScrollable && isExpanded"
|
|
v-show="!scrollEnd"
|
|
class="absolute bg-gradient-to-t from-n-background w-full h-12 to-transparent -bottom-1 pointer-events-none flex items-end justify-end px-2 animate-fade-in-up"
|
|
>
|
|
<Icon
|
|
icon="i-woot-chevrons-down"
|
|
class="w-4 h-6 text-n-slate-9 opacity-50 group-hover:opacity-100"
|
|
/>
|
|
</div>
|
|
</ul>
|
|
</template>
|
|
</li>
|
|
</template>
|