Files
nocodb/packages/nc-gui/components/nc/ListWithSearch.vue
Anbarasu 0bb5ab9982 feat: button field (#9144)
* feat: static button type

* fix: swagger

* fix: style corrections

* feat: webhook trigger

* fix: disables

* feat: Button icons fix: row Delete failing

* fix: expanded-form ux fix: disable buttons in forms fix: update PlainCell button handling fix: webhook delete, active change handling

* fix: disable case

* fix: disable case

* fix: update Button styles

* fix: refactor min/max with limit for columns fix: disable filter, groupby for Button Field fix: disable aggregation for Buttons

* fix: hide button field in Filters

* fix: fields menu corrections fix: update menu corrections

* fix: rebase

* feat: support webhook creation from ButtonOptions

* fix: sort related tests

* fix: keep webhook modal open

* fix: ui fixes

* fix: icon duplicate

* fix: syntax highlighing for handlebar fix: disable mascot fix: truncate selected webhook

* fix: sort disable tooltip

* test: button playwright test

* fix: button column duplication

* fix: add error fix: column options

* fix: EditOrAddProvider.vue

* fix: add invalid configuration

* fix: ux corrections

* fix: ux corrections

* fix: error handling fix: clear single query cache on hook delete

* fix: include button type in api

* fix: update overlay styles fix: webhook update

* fix: formula placeholder

* fix: playwright tests

* fix: playwright tests

* feat: refactor formula input

* fix: added more spacing

* fix: no icon text

* fix: lint

* fix: handle invalid url

* fix: handle sort by for button causes issue when button used as lookup

* fix: button field position

* docs: button field

* fix: tooltip correction

* fix: link

* fix: add btn href

* fix: handle some edge cases

* fix: handle some edge cases

* fix: update font color

* fix: add manual trigger docs

* fix: sqlite BaseModel fix

* docs: button share view info

* fix: rebase

* fix: reduce height and added resize support fix: added tooltip if label overflow

* fix: manual hook disable state

* docs: manual trigger details in webhook page

* fix: chevron grey shade to 500

* docs: sample payload for manual trigger

* fix: style update

* fix: pr review comments

* fix: pr review changes

* fix: reactivity issue

* fix: reactivity issue

* fix: filter enabled on button

* fix: reload meta on webhook change

* fix: error handling in formula filter

* fix: handle url error

---------

Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
2024-08-14 15:32:19 +05:30

192 lines
4.9 KiB
Vue

<script setup lang="ts" generic="T">
const props = withDefaults(
defineProps<{
// As we need to focus search box when the parent is opened
isParentOpen: boolean
searchInputPlaceholder?: string
optionConfig: {
optionClassName: string
selectOptionEvent: string[] | undefined
}
options: Array<T>
disableMascot?: boolean
selectedOptionId?: string
uniqueIdentifier?: keyof T
filterField: keyof T
showSelectedOption?: boolean
}>(),
{
uniqueIdentifier: 'id' as keyof T,
},
)
const emits = defineEmits<{ (e: 'selected', value: T): void }>()
defineSlots<{
default: (props: { option: T }) => any
bottom: () => any
}>()
const {
isParentOpen,
searchInputPlaceholder,
selectedOptionId,
showSelectedOption,
filterField,
options,
optionConfig,
disableMascot,
uniqueIdentifier,
} = toRefs(props)
const inputRef = ref()
const activeOptionIndex = ref(-1)
const searchQuery = ref('')
const { t } = useI18n()
const wrapperRef = ref()
const handleAutoScrollOption = () => {
const option = document.querySelector('.nc-unified-list-option-active')
if (option) {
setTimeout(() => {
option?.scrollIntoView({ behavior: 'smooth', block: 'center' })
}, 50)
}
}
const filteredOptions = computed(
() =>
options.value?.filter((c: T) => (c[filterField.value] as string)?.toLowerCase().includes(searchQuery.value.toLowerCase())) ??
[],
)
const onArrowDown = () => {
activeOptionIndex.value = Math.min(activeOptionIndex.value + 1, filteredOptions.value.length - 1)
handleAutoScrollOption()
}
const onArrowUp = () => {
activeOptionIndex.value = Math.max(activeOptionIndex.value - 1, 0)
handleAutoScrollOption()
}
const onClick = (column: T) => {
if (!column) return
emits('selected', column)
}
const handleKeydownEnter = () => {
if (filteredOptions.value[activeOptionIndex.value]) {
onClick(filteredOptions.value[activeOptionIndex.value])
} else if (filteredOptions.value[0]) {
onClick(filteredOptions.value[activeOptionIndex.value])
}
}
onMounted(() => {
searchQuery.value = ''
activeOptionIndex.value = -1
})
watch(
isParentOpen,
() => {
if (!isParentOpen.value) return
searchQuery.value = ''
setTimeout(() => {
inputRef.value?.focus()
}, 100)
},
{
immediate: true,
},
)
</script>
<template>
<div
ref="wrapperRef"
class="flex flex-col pt-2 nc-list-with-search w-64"
@keydown.arrow-down.prevent="onArrowDown"
@keydown.arrow-up.prevent="onArrowUp"
@keydown.enter.prevent="onClick(filteredOptions[activeOptionIndex])"
>
<div class="w-full pb-2 px-2" @click.stop>
<a-input
ref="inputRef"
v-model:value="searchQuery"
:placeholder="searchInputPlaceholder || t('placeholder.search')"
class="nc-dropdown-search-unified-input"
@keydown.enter.stop="handleKeydownEnter"
@change="activeOptionIndex = 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-unified-search-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-center text-gray-500 flex flex-col items-center gap-6">
<img
v-if="!disableMascot"
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="optionConfig.selectOptionEvent"
class="flex w-full py-[5px] items-center justify-between px-2 hover:bg-gray-100 cursor-pointer rounded-md"
:class="[
`${optionConfig.optionClassName}`,
`nc-unified-list-option-${index}`,
{
'bg-gray-100 nc-unified-list-option-active': activeOptionIndex === index,
},
]"
@click="onClick(option)"
>
<div
class="flex items-center gap-x-1.5"
:class="{
'max-w-[calc(100%_-_28px)]': showSelectedOption,
'max-w-full': !showSelectedOption,
}"
>
<slot :option="option" />
<NcTooltip class="truncate" show-on-truncate-only>
<template #title> {{ option.title }}</template>
<span>
{{ option.title }}
</span>
</NcTooltip>
</div>
<GeneralIcon
v-if="showSelectedOption && option[uniqueIdentifier] === selectedOptionId"
icon="check"
class="flex-none text-primary w-4 h-4"
/>
</div>
<slot name="bottom" />
</div>
</div>
</template>
<style scoped lang="scss">
.nc-unified-search-list-wrapper {
max-height: min(400px, calc(100vh - 120px));
}
</style>