import { makeComponentProps } from '@/composables/component' import { computed, ref, watch, watchEffect } from 'vue' import { UIcon } from '@/components/UIcon/UIcon' import { UListItem } from '@/components/UListItemGroup/UListItem' import { genericComponent, propsFactory, useRender } from '@/utils' export const makeUListItemGroupProps = propsFactory( { disabled: { type: Boolean, default: false, }, position: { type: String, default: 'bottom', }, type: { type: String, default: 'default', }, color: { type: String, default: 'transparent', }, listItems: { type: Array<{ title: string badge: number itemId: string prependIcon: string }>, required: true, }, currentItemId: String, prependIcon: String, ...makeComponentProps(), }, 'UListItemGroup' ) export type UListItemGroupSlots = { default: never header: never } export const UListItemGroup = genericComponent()({ name: 'UListItemGroup', props: makeUListItemGroupProps(), emits: { click: (e: MouseEvent) => true, currentItemId: (value: string) => true, }, setup(props, { emit, slots }) { const isActive = ref(false) const isHover = ref(false) const itemsListRef = ref(null) type listItem = { title: string badge: number itemId: string prependIcon: string } const getListItems = (): listItem[] => { const listItems: listItem[] = props.listItems if (Array.isArray(listItems)) { const updatedItems = listItems.map((item) => { if (item.title && item.itemId && item.prependIcon && item.badge) { return item } else { return { title: item.title || '', badge: item.badge || 0, itemId: item.itemId || '', prependIcon: item.prependIcon || '', } } }) const filteredItems = updatedItems.filter((item) => { return ( item.title !== '' || item.badge !== 0 || item.itemId !== '' || item.prependIcon !== '' ) }) return filteredItems } else { return [] } } function changeCurrentIndex(id: string) { if (props.disabled) return emit('currentItemId', id) } function onListItemClick(e: MouseEvent) { if (props.disabled) return emit('click', e) } const renderListItems = (): JSX.Element[] => { const filteredItems = getListItems() return filteredItems.map((item, index) => ( {item.title} )) } const navItemClasses = computed(() => ({ 'flex items-center p-3 w-full rounded-md overflow-hidden': true, ...(!props.disabled && { 'cursor-pointer active:shadow-xs-btn active:shadow-gray-100': true, }), ...(props.disabled && { 'cursor-not-allowed': true, }), ...(isActive.value === false && !props.disabled && { 'active:bg-transparent hover:bg-gray-50': true, }), ...(isActive.value === true && !props.disabled && { 'bg-gray-200 hover:bg-gray-100 active:bg-gray-50': true, }), ...(props.type !== 'small' && { 'justify-between': true, }), ...(props.type === 'small' && !props.disabled && { 'h-48 max-w-[48px] flex-shrink-none justify-center': true, }), })) const navItemTextClasses = computed(() => ({ 'text-text-md font-semibold select-none whitespace-nowrap': true, ...(isHover.value === false && { 'text-gray-700': true, }), ...(isHover.value === true && { 'text-gray-700': true, }), ...(isActive.value === true && { 'text-gray-700': true, }), })) const chevronIconClasses = computed(() => ({ 'transition-transform': true, ...(isActive.value === true && props.position === 'bottom' && !props.disabled && { 'rotate-180': true, }), ...(isActive.value === true && props.position !== 'bottom' && !props.disabled && { 'opacity-0': true, }), })) const itemsListHeight = () => { const element = itemsListRef.value if (element) { return (element.scrollHeight + 16).toString() } return '0' } function getParentWidth() { const element = itemsListRef.value if (element) { let parentElement = element.parentElement while ( parentElement && getComputedStyle(parentElement).position !== 'relative' ) { parentElement = parentElement.parentElement } if (parentElement) { return parentElement.clientWidth } else { return 0 } } else { return 0 } } watch(isActive, (newValue) => { if ( getListItems().length !== 0 && props.position == 'bottom' && !props.disabled ) { const element = itemsListRef.value if (element) { if (newValue) { element.style.transition = 'max-height 0.15s ease-in-out, padding 0.15s ease-in-out' element.style.paddingTop = '8px' element.style.paddingBottom = '8px' element.style.maxHeight = `${itemsListHeight()}px` } else { element.style.transition = 'max-height 0.15s ease-in-out, padding 0.15s ease-in-out' element.style.paddingTop = '' element.style.paddingBottom = '' element.style.maxHeight = '' } } } if ( getListItems().length !== 0 && props.position == 'right' && !props.disabled ) { const element = itemsListRef.value if (element) { if (newValue) { element.style.transition = 'transform 0.15s ease-in-out, opacity 0.15s ease-in-out' element.style.position = 'absolute' element.style.top = '0' element.style.left = `${getParentWidth()}px` element.style.overflowY = `visible` element.style.visibility = `visible` element.style.paddingTop = '36px' element.style.opacity = `1` element.style.maxHeight = `100%` element.style.height = `100%` } else { element.style.transition = 'transform 0.15s ease-in-out, opacity 0.15s ease-in-out' element.style.opacity = `0` element.style.visibility = `visible` setTimeout(() => { element.style.visibility = 'hidden' element.style.transition = '' }, 150) } } } if ( getListItems().length !== 0 && props.position == 'left' && !props.disabled ) { const element = itemsListRef.value if (element) { if (newValue) { element.style.transition = 'transform 0.15s ease-in-out, opacity 0.15s ease-in-out' element.style.position = 'absolute' element.style.top = '0' element.style.right = `${getParentWidth()}px` element.style.overflowY = `visible` element.style.visibility = `visible` element.style.paddingTop = '36px' element.style.opacity = `1` element.style.maxHeight = `100%` element.style.height = `100%` } else { element.style.transition = 'transform 0.15s ease-in-out, opacity 0.15s ease-in-out' element.style.opacity = `0` element.style.visibility = `visible` setTimeout(() => { element.style.visibility = 'hidden' element.style.transition = '' }, 150) } } } if ( getListItems().length !== 0 && props.position !== 'left' && props.type == 'small' && !props.disabled ) { const element = itemsListRef.value if (element) { if (newValue) { element.style.transition = 'transform 0.15s ease-in-out, opacity 0.15s ease-in-out' element.style.position = 'absolute' element.style.top = '0' element.style.left = `76px` element.style.width = `${getParentWidth() - 76}px` element.style.overflowY = `visible` element.style.visibility = `visible` element.style.paddingTop = '36px' element.style.opacity = `1` element.style.maxHeight = `100%` element.style.height = `100%` } else { element.style.transition = 'transform 0.15s ease-in-out, opacity 0.15s ease-in-out' element.style.opacity = `0` element.style.visibility = `visible` element.style.paddingTop = '36px' setTimeout(() => { element.style.visibility = 'hidden' element.style.transition = '' }, 150) } } } if ( getListItems().length !== 0 && props.position === 'left' && props.type == 'small' && !props.disabled ) { const element = itemsListRef.value if (element) { if (newValue) { element.style.transition = 'transform 0.15s ease-in-out, opacity 0.15s ease-in-out' element.style.position = 'absolute' element.style.top = '0' element.style.right = `76px` element.style.width = `${getParentWidth() - 76}px` element.style.overflowY = `visible` element.style.visibility = `visible` element.style.paddingTop = '36px' element.style.opacity = `1` element.style.maxHeight = `100%` element.style.height = `100%` } else { element.style.transition = 'transform 0.15s ease-in-out, opacity 0.15s ease-in-out' element.style.position = 'absolute' element.style.opacity = `0` element.style.visibility = `visible` setTimeout(() => { element.style.visibility = 'hidden' element.style.transition = '' }, 150) } } } }) watchEffect(() => { if (props.type !== 'small') { const element = itemsListRef.value if (element) { element.style.transition = '' element.style.opacity = '' element.style.visibility = '' element.style.position = '' element.style.overflowY = '' element.style.paddingTop = '' element.style.width = '' } } }) const itemsListClasses = computed(() => ({ // eslint-disable-next-line max-len 'w-calc-100%-plus-8px flex flex-col gap-1 px-1 ml-[-4px] overflow-y-hidden transition-max-height max-h-0 select-none': true, ...(props.position !== 'bottom' && { 'opacity-0': true, }), ...(props.type === 'small' && props.position !== 'bottom' && { 'w-[0px] px-3': true, }), [`bg-${props.color}`]: true, })) const subTitleClasses = computed(() => ({ 'text-md text-gray-900 px-3 pb-4 font-medium': true, })) const prependIconColor = () => { if (isActive.value === true || isHover.value === true) { return 'gray-500' } return 'gray-500' } const chevronIconColor = () => { if ( (isActive.value === true || isHover.value === true) && !props.disabled ) { return 'gray-400' } return 'gray-500' } useRender(() => (
(isHover.value = true)} onMouseleave={() => (isHover.value = false)} onClick={() => (isActive.value = !isActive.value)} class={[navItemClasses.value, 'item']} >
{props.prependIcon ? ( ) : null} {slots.default && props.type !== 'small' && (
{slots.default?.()}
)}
{getListItems().length !== 0 && props.type !== 'small' ? (
) : null}
{slots.default && props.type === 'small' && (
{slots.default?.()}
)} {renderListItems()}
)) return { isActive, } }, }) export type UListItemGroup = InstanceType