Files
nocodb/packages/nc-gui/components/dashboard/MiniSidebarV2/RailItem.vue
2026-03-04 14:42:39 +00:00

136 lines
3.1 KiB
Vue

<script lang="ts" setup>
interface Props {
label?: string
tooltip?: string
disableTooltip?: boolean
icon?: string
activeIcon?: string
active?: boolean
disabled?: boolean
/** Dropdown trigger — active state shows hover bg only, no indicator or text color */
isDropdown?: boolean
panelKey?: string
}
const props = withDefaults(defineProps<Props>(), {
label: '',
tooltip: '',
icon: undefined,
activeIcon: undefined,
active: false,
disabled: false,
isDropdown: false,
panelKey: undefined,
})
const emits = defineEmits<{
(e: 'click'): void
}>()
const tooltipText = computed(() => props.tooltip || props.label)
const currentIcon = computed(() => {
if (props.active && props.activeIcon) return props.activeIcon
return props.icon
})
</script>
<template>
<NcTooltip
class="w-full flex justify-center relative"
placement="right"
:arrow="false"
:disabled="!tooltipText || disableTooltip"
>
<template #title>{{ tooltipText }}</template>
<div
class="nc-rail-item"
:class="{ active, disabled, 'is-dropdown': isDropdown }"
:data-panel="panelKey"
@click="!disabled && emits('click')"
>
<!-- Active indicator bar -->
<span class="nc-rail-item-indicator" />
<slot v-if="$slots.default" />
<template v-else>
<slot name="icon">
<GeneralIcon v-if="currentIcon" :icon="(currentIcon as any)" class="nc-rail-item-icon" />
</slot>
</template>
<span v-if="label || $slots.label" class="nc-rail-item-label">
<slot name="label">{{ label }}</slot>
</span>
</div>
</NcTooltip>
</template>
<style lang="scss" scoped>
.nc-rail-item {
@apply flex flex-col gap-1.5 items-center justify-center pt-2.5 pb-1.5 cursor-pointer transition-all duration-150 rounded-[10px];
width: 53px;
&:not(.active) {
@apply text-nc-content-gray-muted;
}
.nc-rail-item-indicator {
@apply absolute left-0 top-1/2 transform -translate-y-1/2 w-[3px] h-[36px] opacity-0 pointer-events-none rounded-r-sm;
@apply bg-nc-content-brand;
transition: opacity 0.2s;
}
.nc-rail-item-icon {
@apply h-4 w-4 flex items-center justify-center;
}
.nc-rail-item-label {
@apply select-none text-captionXs text-[9px] font-medium leading-tight tracking-tight opacity-85;
}
&:hover:not(.active):not(.disabled) {
@apply text-nc-content-subtle2;
background: rgba(0, 0, 0, 0.05);
:root[theme='dark'] & {
background: rgba(255, 255, 255, 0.05);
}
}
// Normal active state: brand color text + indicator
&.active:not(.is-dropdown) {
@apply text-nc-content-brand;
background: rgba(0, 0, 0, 0.08);
:root[theme='dark'] & {
background: rgba(255, 255, 255, 0.08);
}
.nc-rail-item-indicator {
opacity: 1;
}
.nc-rail-item-label {
opacity: 1;
}
}
// Dropdown active state: hover bg only, no indicator or text color change
&.is-dropdown.active {
@apply text-nc-content-gray-muted;
background: rgba(0, 0, 0, 0.05);
:root[theme='dark'] & {
background: rgba(255, 255, 255, 0.05);
}
}
&.disabled {
@apply opacity-40 cursor-not-allowed;
}
}
</style>