mirror of https://github.com/nocodb/nocodb
Ramesh Mane
8 months ago
committed by
GitHub
20 changed files with 440 additions and 333 deletions
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,184 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import type { ColumnType } from 'nocodb-sdk' |
||||||
|
|
||||||
|
const props = defineProps<{ |
||||||
|
// As we need to focus search box when the parent is opened |
||||||
|
isParentOpen: boolean |
||||||
|
toolbarMenu: 'groupBy' | 'sort' | 'globalSearch' |
||||||
|
searchInputPlaceholder?: string |
||||||
|
selectedOptionId?: string |
||||||
|
options: ColumnType[] |
||||||
|
showSelectedOption?: boolean |
||||||
|
}>() |
||||||
|
|
||||||
|
const emits = defineEmits<{ selected: [ColumnType] }>() |
||||||
|
|
||||||
|
const { isParentOpen, toolbarMenu, searchInputPlaceholder, selectedOptionId, options, showSelectedOption } = toRefs(props) |
||||||
|
|
||||||
|
const searchQuery = ref('') |
||||||
|
|
||||||
|
const filteredOptions = computed( |
||||||
|
() => options.value?.filter((c: ColumnType) => c.title?.toLowerCase().includes(searchQuery.value.toLowerCase())) ?? [], |
||||||
|
) |
||||||
|
|
||||||
|
const inputRef = ref() |
||||||
|
|
||||||
|
const activeFieldIndex = ref(-1) |
||||||
|
|
||||||
|
const configByToolbarMenu = computed(() => { |
||||||
|
switch (toolbarMenu.value) { |
||||||
|
case 'groupBy': |
||||||
|
return { |
||||||
|
selectOptionEvent: ['c:group-by:add:column:select'], |
||||||
|
optionClassName: 'nc-group-by-column-search-item', |
||||||
|
} |
||||||
|
case 'sort': |
||||||
|
return { |
||||||
|
selectOptionEvent: ['c:sort:add:column:select'], |
||||||
|
optionClassName: 'nc-sort-column-search-item', |
||||||
|
} |
||||||
|
case 'globalSearch': |
||||||
|
return { |
||||||
|
selectOptionEvent: ['c:search:field:select'], |
||||||
|
optionClassName: '', |
||||||
|
} |
||||||
|
default: |
||||||
|
return { |
||||||
|
selectOptionEvent: undefined, |
||||||
|
optionClassName: '', |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const onClick = (column: ColumnType) => { |
||||||
|
if (!column) return |
||||||
|
|
||||||
|
emits('selected', column) |
||||||
|
} |
||||||
|
|
||||||
|
const handleAutoScrollOption = () => { |
||||||
|
const option = document.querySelector('.nc-field-list-option-active') |
||||||
|
|
||||||
|
if (option) { |
||||||
|
setTimeout(() => { |
||||||
|
option?.scrollIntoView({ behavior: 'smooth', block: 'center' }) |
||||||
|
}, 50) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const onArrowDown = () => { |
||||||
|
activeFieldIndex.value = Math.min(activeFieldIndex.value + 1, filteredOptions.value.length - 1) |
||||||
|
handleAutoScrollOption() |
||||||
|
} |
||||||
|
|
||||||
|
const onArrowUp = () => { |
||||||
|
activeFieldIndex.value = Math.max(activeFieldIndex.value - 1, 0) |
||||||
|
handleAutoScrollOption() |
||||||
|
} |
||||||
|
|
||||||
|
const handleKeydownEnter = () => { |
||||||
|
if (filteredOptions.value[activeFieldIndex.value]) { |
||||||
|
onClick(filteredOptions.value[activeFieldIndex.value]) |
||||||
|
} else if (filteredOptions.value[0]) { |
||||||
|
onClick(filteredOptions.value[activeFieldIndex.value]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
searchQuery.value = '' |
||||||
|
activeFieldIndex.value = -1 |
||||||
|
}) |
||||||
|
|
||||||
|
watch( |
||||||
|
isParentOpen, |
||||||
|
() => { |
||||||
|
if (!isParentOpen.value) return |
||||||
|
|
||||||
|
searchQuery.value = '' |
||||||
|
setTimeout(() => { |
||||||
|
inputRef.value?.focus() |
||||||
|
}, 100) |
||||||
|
}, |
||||||
|
{ |
||||||
|
immediate: true, |
||||||
|
}, |
||||||
|
) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div |
||||||
|
class="flex flex-col pt-2 w-64" |
||||||
|
@keydown.arrow-down.prevent="onArrowDown" |
||||||
|
@keydown.arrow-up.prevent="onArrowUp" |
||||||
|
@keydown.enter.prevent="onClick(filteredOptions[activeFieldIndex])" |
||||||
|
> |
||||||
|
<div class="w-full pb-2 px-2" @click.stop> |
||||||
|
<a-input |
||||||
|
ref="inputRef" |
||||||
|
v-model:value="searchQuery" |
||||||
|
:placeholder="searchInputPlaceholder || $t('placeholder.searchFields')" |
||||||
|
class="nc-toolbar-dropdown-search-field-input" |
||||||
|
@keydown.enter.stop="handleKeydownEnter" |
||||||
|
@change="activeFieldIndex = 0" |
||||||
|
> |
||||||
|
<template #prefix> <GeneralIcon icon="search" class="nc-search-icon h-3.5 w-3.5 mr-1" /> </template |
||||||
|
></a-input> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="nc-field-list-wrapper flex-col w-full max-h-100 nc-scrollbar-thin !overflow-y-auto px-2 pb-2"> |
||||||
|
<div v-if="!filteredOptions.length" class="px-2 py-6 text-gray-500 flex flex-col items-center gap-6"> |
||||||
|
<img |
||||||
|
src="~assets/img/placeholder/no-search-result-found.png" |
||||||
|
class="!w-[164px] flex-none" |
||||||
|
alt="No search results found" |
||||||
|
/> |
||||||
|
|
||||||
|
{{ options.length ? $t('title.noResultsMatchedYourSearch') : 'The list is empty' }} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div |
||||||
|
v-for="(option, index) in filteredOptions" |
||||||
|
:key="index" |
||||||
|
v-e="configByToolbarMenu.selectOptionEvent" |
||||||
|
class="flex w-full py-[5px] items-center justify-between px-2 hover:bg-gray-100 cursor-pointer rounded-md" |
||||||
|
:class="[ |
||||||
|
`${configByToolbarMenu.optionClassName}`, |
||||||
|
`nc-field-list-option-${index}`, |
||||||
|
{ |
||||||
|
'bg-gray-100 nc-field-list-option-active': activeFieldIndex === index, |
||||||
|
}, |
||||||
|
]" |
||||||
|
@click="onClick(option)" |
||||||
|
> |
||||||
|
<div |
||||||
|
class="flex items-center gap-x-1.5" |
||||||
|
:class="{ |
||||||
|
'max-w-[calc(100%_-_28px)]': showSelectedOption, |
||||||
|
'max-w-full': !showSelectedOption, |
||||||
|
}" |
||||||
|
> |
||||||
|
<SmartsheetHeaderIcon :column="option" class="!w-3.5 !h-3.5 !text-gray-500" /> |
||||||
|
<NcTooltip class="truncate" show-on-truncate-only> |
||||||
|
<template #title> {{ option.title }}</template> |
||||||
|
<span> |
||||||
|
{{ option.title }} |
||||||
|
</span> |
||||||
|
</NcTooltip> |
||||||
|
</div> |
||||||
|
|
||||||
|
<GeneralIcon |
||||||
|
v-if="showSelectedOption && option.id === selectedOptionId" |
||||||
|
id="nc-selected-item-icon" |
||||||
|
icon="check" |
||||||
|
class="flex-none text-primary w-4 h-4" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.nc-field-list-wrapper { |
||||||
|
max-height: min(400px, calc(100vh - 120px)); |
||||||
|
} |
||||||
|
</style> |
Loading…
Reference in new issue