import './VNavigationDrawer.scss' import { useSticky } from './sticky' import { useTouch } from './touch' import { makeLayoutItemProps, useLayoutItem } from '@/composables/layout' import { useProxiedModel } from '@/composables/proxiedModel' import { useRouter } from '@/composables/router' import { useScopeId } from '@/composables/scopeId' import { makeTagProps } from '@/composables/tag' import { makeComponentProps } from '@/composables/component' import { computed, nextTick, onBeforeMount, ref, shallowRef, toRef, Transition, watch, ExtractPropTypes, PropType, onMounted, } from 'vue' import { genericComponent, propsFactory, useRender } from '@/utils' import { provideDefaults } from '@/composables/defaults' export type USidebarSlots = { default: never prepend: never append: never } const locations = ['start', 'end', 'left', 'right', 'top', 'bottom'] as const export const makeUSidebarProps = propsFactory( { variant: String, theme: String, disableResizeWatcher: Boolean, disableRouteWatcher: Boolean, expandOnHover: Boolean, floating: Boolean, transitionDuration: { type: Number, default: 500, }, border: { type: Boolean, default: false, }, elevation: { type: Boolean, default: false, }, modelValue: { type: Boolean, default: false, required: false, }, permanent: Boolean, rail: { type: Boolean as PropType, default: null, }, railWidth: { type: [Number, String], default: 56, }, scrim: { type: [Boolean, String], default: true, }, temporary: Boolean, touchless: Boolean, width: { type: [Number, String], default: 312, }, location: { type: String as PropType<(typeof locations)[number]>, default: 'start', validator: (value: any) => locations.includes(value), }, sticky: Boolean, ...makeComponentProps(), ...makeLayoutItemProps(), ...makeTagProps({ tag: 'nav' }), }, 'USidebar' ) export type USidebarProps = ExtractPropTypes export const USidebar = genericComponent()({ name: 'USidebar', props: makeUSidebarProps(), emits: { 'update:modelValue': (val: boolean) => true, 'update:rail': (val: boolean) => true, }, setup(props, { attrs, emit, slots }) { const router = useRouter() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const isActive = useProxiedModel(props, 'modelValue', null, (v) => !!v) const { scopeId } = useScopeId() const rootEl = ref() const isHovering = shallowRef(false) const screenWidth = ref(0) const bgColor = computed(() => { let bgColor = '' if ( props.theme === 'light' && (props.variant === 'gray' || props.variant === 'primary') ) { bgColor = 'white' } else if (props.theme === 'dark' && props.variant === 'primary') { bgColor = 'primary-700' } else if (props.theme === 'dark' && props.variant === 'gray') { bgColor = 'gray-900' } return bgColor }) onMounted(() => { screenWidth.value = window.innerWidth }) const mobile = computed(() => { return screenWidth.value < 350 ? true : false }) const width = computed(() => { return props.rail && props.expandOnHover && isHovering.value ? Number(props.width) : Number(props.rail ? props.railWidth : props.width) }) const location = computed(() => { return props.location as 'left' | 'right' | 'bottom' }) const isTemporary = computed( () => !props.permanent && (mobile.value || props.temporary) ) const isSticky = computed( () => props.sticky && !isTemporary.value && location.value !== 'bottom' ) if (props.expandOnHover && props.rail != null) { watch(isHovering, (val) => emit('update:rail', !val)) } if (!props.disableResizeWatcher) { watch( isTemporary, (val) => !props.permanent && nextTick(() => (isActive.value = !val)) ) } if (!props.disableRouteWatcher && router) { watch( router.currentRoute, () => isTemporary.value && (isActive.value = false) ) } watch( () => props.permanent, (val) => { if (val) isActive.value = true } ) onBeforeMount(() => { if (props.modelValue != null || isTemporary.value) return isActive.value = props.permanent || !mobile.value }) const { isDragging, dragProgress, dragStyles } = useTouch({ isActive, isTemporary, width, touchless: toRef(props, 'touchless'), position: location, }) const layoutSize = computed(() => { const size = isTemporary.value ? 0 : props.rail && props.expandOnHover ? Number(props.railWidth) : width.value return isDragging.value ? size * dragProgress.value : size }) const { layoutItemStyles, layoutItemScrimStyles } = useLayoutItem({ id: props.name, order: computed(() => parseInt(props.order, 10)), position: location, layoutSize, elementSize: width, active: computed(() => isActive.value || isDragging.value), disableTransitions: computed(() => isDragging.value), absolute: computed( () => // eslint-disable-next-line @typescript-eslint/no-use-before-define props.absolute || (isSticky.value && typeof isStuck.value !== 'string') ), }) const { isStuck, stickyStyles } = useSticky({ rootEl, isSticky, layoutItemStyles, }) const scrimStyles = computed(() => ({ ...(isDragging.value ? { opacity: dragProgress.value * 0.2, transition: 'none', } : undefined), ...layoutItemScrimStyles.value, })) provideDefaults({ VList: { bgColor: 'transparent', }, }) function onMouseenter() { isHovering.value = true } function onMouseleave() { isHovering.value = false } const flexContainerClasses = computed(() => { let classes = '' if (props.location === 'top') { classes = 'flex justify-between items-center h-fit' } else { classes = 'flex flex-col justify-between h-full' } return classes }) const borderClasses = computed(() => { let border = '' if (!props.border) { return null } else if ( props.border && props.location === 'left' && props.theme === 'light' ) { border = `border-gray-200 border-r-1` } else if ( props.border && props.location === 'right' && props.theme === 'light' ) { border = `border-gray-200 border-l-1` } else if ( props.border && props.location === 'top' && props.theme === 'light' ) { border = `border-gray-200 border-b-1` } return border }) const elevationClasses = computed(() => { let shadow = '' if (!props.elevation) { return null } else if (props.elevation && props.temporary) { shadow = `shadow-xl shadow-gray-900` } return shadow }) useRender(() => { return ( <> {slots.prepend && ( )}
{slots.default?.()}
{slots.append && ( )}
{isTemporary.value && (isDragging.value || isActive.value) && !!props.scrim && (
(isActive.value = false)} {...scopeId} /> )} ) }) return { isStuck, isHovering, isActive, } }, }) export type USidebar = InstanceType