Browse Source

Merge pull request #8188 from nocodb/nc-feat/update-toolbar-menu-dropdown-with-new-design

Nc feat/update toolbar menu dropdown with new design
pull/8228/head
Ramesh Mane 6 months ago committed by GitHub
parent
commit
ef132ff4bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. BIN
      packages/nc-gui/assets/img/placeholder/no-search-result-found.png
  2. 64
      packages/nc-gui/assets/style.scss
  3. 4
      packages/nc-gui/components/nc/Select.vue
  4. 26
      packages/nc-gui/components/nc/Switch.vue
  5. 7
      packages/nc-gui/components/smartsheet/Form.vue
  6. 24
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  7. 74
      packages/nc-gui/components/smartsheet/toolbar/CreateGroupBy.vue
  8. 74
      packages/nc-gui/components/smartsheet/toolbar/CreateSort.vue
  9. 4
      packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue
  10. 184
      packages/nc-gui/components/smartsheet/toolbar/FieldListWithSearch.vue
  11. 130
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  12. 23
      packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue
  13. 2
      packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue
  14. 106
      packages/nc-gui/components/smartsheet/toolbar/SearchData.vue
  15. 24
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  16. 5
      packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
  17. 7
      packages/nc-gui/composables/useViewColumns.ts
  18. 3
      packages/nc-gui/lang/en.json
  19. 2
      packages/nc-gui/windi.config.ts
  20. 4
      tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts

BIN
packages/nc-gui/assets/img/placeholder/no-search-result-found.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

64
packages/nc-gui/assets/style.scss

@ -37,7 +37,7 @@ body {
} }
.rc-virtual-list-holder-inner { .rc-virtual-list-holder-inner {
@apply !px-1.5 @apply !px-1.5;
} }
.ant-layout-header { .ant-layout-header {
height: var(--topbar-height) !important; height: var(--topbar-height) !important;
@ -51,13 +51,17 @@ main {
@apply m-0 h-full w-full bg-white; @apply m-0 h-full w-full bg-white;
} }
.nc-input-md { .nc-input-md {
@apply !rounded-lg !py-2 !px-3 mb-1; @apply !rounded-lg !py-2 !px-3 mb-1;
} }
.mobile { .mobile {
.nc-scrollbar-md, .nc-scrollbar-lg, .nc-scrollbar-x-md, .nc-scrollbar-dark-md, .nc-scrollbar-x-md-dark, .nc-scrollbar-x-lg { .nc-scrollbar-md,
.nc-scrollbar-lg,
.nc-scrollbar-x-md,
.nc-scrollbar-dark-md,
.nc-scrollbar-x-md-dark,
.nc-scrollbar-x-lg {
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 0px; width: 0px;
} }
@ -116,7 +120,6 @@ main {
overflow-x: auto !important; overflow-x: auto !important;
scrollbar-width: thin !important; scrollbar-width: thin !important;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 4px; width: 4px;
height: 4px; height: 4px;
@ -131,7 +134,6 @@ main {
-webkit-border-radius: 10px; -webkit-border-radius: 10px;
border-radius: 10px; border-radius: 10px;
width: 4px; width: 4px;
@apply bg-gray-200; @apply bg-gray-200;
} }
@ -145,7 +147,6 @@ main {
overflow-x: hidden; overflow-x: hidden;
scrollbar-width: thin !important; scrollbar-width: thin !important;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 4px; width: 4px;
height: 4px; height: 4px;
@ -177,7 +178,6 @@ main {
overflow-x: auto !important; overflow-x: auto !important;
scrollbar-width: thin !important; scrollbar-width: thin !important;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 4px; width: 4px;
height: 4px; height: 4px;
@ -192,14 +192,11 @@ main {
-webkit-border-radius: 10px; -webkit-border-radius: 10px;
border-radius: 10px; border-radius: 10px;
width: 4px; width: 4px;
background-color: rgba(0, 0, 0, 0.3) background-color: rgba(0, 0, 0, 0.3);
} }
&::-webkit-scrollbar-thumb:hover { &::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.4) background-color: rgba(0, 0, 0, 0.4);
} }
} }
@ -220,7 +217,6 @@ main {
-webkit-border-radius: 10px; -webkit-border-radius: 10px;
border-radius: 10px; border-radius: 10px;
width: 8px; width: 8px;
@apply bg-gray-200; @apply bg-gray-200;
} }
@ -256,10 +252,10 @@ a {
@apply !w-1; @apply !w-1;
} }
.rc-virtual-list-scrollbar-thumb{ .rc-virtual-list-scrollbar-thumb {
@apply !bg-gray-200; @apply !bg-gray-200;
&:hover{ &:hover {
@apply !bg-gray-300; @apply !bg-gray-300;
} }
} }
@ -466,8 +462,8 @@ a {
.ant-dropdown-menu-submenu { .ant-dropdown-menu-submenu {
@apply !py-0; @apply !py-0;
&.ant-dropdown-menu-submenu-popup{ &.ant-dropdown-menu-submenu-popup {
@apply border-1 border-gray-200 @apply border-1 border-gray-200;
} }
.ant-dropdown-menu, .ant-dropdown-menu,
.ant-menu { .ant-menu {
@ -545,11 +541,11 @@ a {
@apply bg-gray-300 bg-opacity-20; @apply bg-gray-300 bg-opacity-20;
} }
.ant-select-item-option:hover{ .ant-select-item-option:hover {
@apply !bg-gray-100; @apply !bg-gray-100;
} }
.ant-select-item-option-selected{ .ant-select-item-option-selected {
@apply !bg-white; @apply !bg-white;
} }
/* Hide the element with id nc-selected-item-icon */ /* Hide the element with id nc-selected-item-icon */
@ -658,7 +654,7 @@ a {
} }
.nc-toolbar-dropdown { .nc-toolbar-dropdown {
@apply !rounded-2xl; @apply !rounded-lg;
} }
input[type='number'] { input[type='number'] {
@ -712,7 +708,8 @@ input[type='number'] {
.nc-emoji { .nc-emoji {
@apply xs:(text-lg); @apply xs:(text-lg);
} }
.material-symbols, .nc-icon { .material-symbols,
.nc-icon {
@apply !xs:(text-xl -mt-0.25); @apply !xs:(text-xl -mt-0.25);
} }
@ -729,7 +726,6 @@ input[type='number'] {
@apply opacity-0 group-hover:(opacity-100) text-gray-600 hover:(bg-gray-400 bg-opacity-20 text-gray-900) duration-100; @apply opacity-0 group-hover:(opacity-100) text-gray-600 hover:(bg-gray-400 bg-opacity-20 text-gray-900) duration-100;
} }
.nc-button.ant-btn.nc-sidebar-node-btn.nc-sidebar-expand { .nc-button.ant-btn.nc-sidebar-node-btn.nc-sidebar-expand {
@apply xs:(opacity-100 hover:bg-gray-50); @apply xs:(opacity-100 hover:bg-gray-50);
@ -740,17 +736,17 @@ input[type='number'] {
.ant-message-notice-content { .ant-message-notice-content {
@apply !rounded-md; @apply !rounded-md;
.ant-message-custom-content{ .ant-message-custom-content {
@apply flex items-center; @apply flex items-center;
} }
} }
svg.nc-cell-icon, svg.nc-virtual-cell-icon { svg.nc-cell-icon,
svg.nc-virtual-cell-icon {
@apply w-1em h-1em flex-none; @apply w-1em h-1em flex-none;
font-size: 1rem; font-size: 1rem;
} }
// For select type field list layout // For select type field list layout
.nc-field-layout-list { .nc-field-layout-list {
@apply !flex !flex-col !items-start w-full !space-y-0.5 !max-w-full; @apply !flex !flex-col !items-start w-full !space-y-0.5 !max-w-full;
@ -786,3 +782,21 @@ svg.nc-cell-icon, svg.nc-virtual-cell-icon {
} }
} }
.nc-toolbar-dropdown-search-field-input {
@apply !rounded-lg;
.nc-search-icon {
@apply text-gray-400;
}
&:hover .nc-search-icon,
&.ant-input-affix-wrapper-focused .nc-search-icon {
@apply text-gray-800;
}
}
// switch - on tab focus show outline
.ant-switch:focus-visible,
.ant-switch-checked:focus-visible {
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #3366ff;
}

4
packages/nc-gui/components/nc/Select.vue

@ -106,7 +106,7 @@ const onChange = (value: string) => {
} }
.nc-select-dropdown { .nc-select-dropdown {
@apply !rounded-xl py-1.5; @apply !rounded-lg py-1.5;
.rc-virtual-list-holder { .rc-virtual-list-holder {
overflow-y: auto; overflow-y: auto;
@ -129,7 +129,7 @@ const onChange = (value: string) => {
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
width: 4px; width: 4px;
@apply bg-gray-300; @apply bg-gray-300 rounded-md;
} }
&::-webkit-scrollbar-thumb:hover { &::-webkit-scrollbar-thumb:hover {
@apply bg-gray-400; @apply bg-gray-400;

26
packages/nc-gui/components/nc/Switch.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
const props = withDefaults(defineProps<{ checked: boolean; disabled?: boolean; size?: 'default' | 'small' }>(), { const props = withDefaults(defineProps<{ checked: boolean; disabled?: boolean; size?: 'default' | 'small' | 'xsmall' }>(), {
size: 'small', size: 'small',
}) })
@ -31,3 +31,27 @@ const onChange = (e: boolean) => {
<slot /> <slot />
</span> </span>
</template> </template>
<style lang="scss" scoped>
.size-xsmall {
@apply h-3.5 min-w-[26px] leading-[14px];
:deep(.ant-switch-handle) {
@apply h-[10px] w-[10px] top-[2px] left-[calc(100%_-_24px)];
}
:deep(.ant-switch-inner) {
@apply !mr-[5px] !ml-[18px] !my-0;
}
&.ant-switch-checked {
:deep(.ant-switch-handle) {
@apply left-[calc(100%_-_12px)];
}
:deep(.ant-switch-inner) {
@apply !mr-[18px] !ml-[5px];
}
}
}
</style>

7
packages/nc-gui/components/smartsheet/Form.vue

@ -2028,13 +2028,6 @@ useEventListener(
} }
} }
} }
.nc-form-wrapper {
.ant-switch:focus-visible,
.ant-switch-checked:focus-visible {
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #3366ff;
}
}
</style> </style>
<style lang="scss"> <style lang="scss">

24
packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue

@ -337,15 +337,16 @@ function isDateType(uidt: UITypes) {
<div <div
class="menu-filter-dropdown" class="menu-filter-dropdown"
:class="{ :class="{
'max-h-[max(80vh,500px)] min-w-112 py-6 pl-6': !nested, 'max-h-[max(80vh,500px)] min-w-112 py-2 pl-4': !nested,
'w-full ': nested, 'w-full ': nested,
'py-4': !filters.length,
}" }"
> >
<div <div
v-if="filters && filters.length" v-if="filters && filters.length"
ref="wrapperDomRef" ref="wrapperDomRef"
class="flex flex-col gap-y-3 nc-filter-grid pb-2 w-full" class="flex flex-col gap-y-3 nc-filter-grid w-full"
:class="{ 'max-h-420px nc-scrollbar-md pr-3.5 nc-filter-top-wrapper': !nested }" :class="{ 'max-h-420px nc-scrollbar-thin nc-filter-top-wrapper pr-4 my-2 py-1': !nested }"
@click.stop @click.stop
> >
<template v-for="(filter, i) in filters" :key="i"> <template v-for="(filter, i) in filters" :key="i">
@ -544,7 +545,14 @@ function isDateType(uidt: UITypes) {
</div> </div>
<template v-if="isEeUI && !isPublic"> <template v-if="isEeUI && !isPublic">
<div v-if="filtersCount < getPlanLimit(PlanLimitTypes.FILTER_LIMIT)" ref="addFiltersRowDomRef" class="flex gap-2"> <div
v-if="filtersCount < getPlanLimit(PlanLimitTypes.FILTER_LIMIT)"
ref="addFiltersRowDomRef"
class="flex gap-2"
:class="{
'mt-1 mb-2': filters.length,
}"
>
<NcButton size="small" type="text" class="!text-brand-500" @click.stop="addFilter()"> <NcButton size="small" type="text" class="!text-brand-500" @click.stop="addFilter()">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<component :is="iconMap.plus" /> <component :is="iconMap.plus" />
@ -563,7 +571,13 @@ function isDateType(uidt: UITypes) {
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div ref="addFiltersRowDomRef" class="flex gap-2"> <div
ref="addFiltersRowDomRef"
class="flex gap-2"
:class="{
'mt-1 mb-2': filters.length,
}"
>
<NcButton size="small" type="text" class="!text-brand-500" @click.stop="addFilter()"> <NcButton size="small" type="text" class="!text-brand-500" @click.stop="addFilter()">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<component :is="iconMap.plus" /> <component :is="iconMap.plus" />

74
packages/nc-gui/components/smartsheet/toolbar/CreateGroupBy.vue

@ -12,12 +12,6 @@ const emits = defineEmits(['created'])
const { isParentOpen, columns } = toRefs(props) const { isParentOpen, columns } = toRefs(props)
const inputRef = ref()
const search = ref('')
const activeFieldIndex = ref(-1)
const activeView = inject(ActiveViewInj, ref()) const activeView = inject(ActiveViewInj, ref())
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
@ -58,72 +52,22 @@ const options = computed<ColumnType[]>(
) )
} }
}) })
.filter((c: ColumnType) => !groupBy.value.find((g) => g.column?.id === c.id)) .filter((c: ColumnType) => !groupBy.value.find((g) => g.column?.id === c.id)) ?? [],
.filter((c: ColumnType) => c.title?.toLowerCase().includes(search.value.toLowerCase())) ?? [],
) )
const onClick = (column: ColumnType) => { const onClick = (column: ColumnType) => {
emits('created', column) emits('created', column)
} }
watch(
isParentOpen,
() => {
if (!isParentOpen.value) return
setTimeout(() => {
inputRef.value?.focus()
}, 100)
},
{
immediate: true,
},
)
onMounted(() => {
search.value = ''
activeFieldIndex.value = -1
})
const onArrowDown = () => {
activeFieldIndex.value = Math.min(activeFieldIndex.value + 1, options.value.length - 1)
}
const onArrowUp = () => {
activeFieldIndex.value = Math.max(activeFieldIndex.value - 1, 0)
}
</script> </script>
<template> <template>
<div <div class="nc-group-by-create-modal">
class="flex flex-col w-full pt-4 pb-2 min-w-64 nc-group-by-create-modal" <SmartsheetToolbarFieldListWithSearch
@keydown.arrow-down.prevent="onArrowDown" :is-parent-open="isParentOpen"
@keydown.arrow-up.prevent="onArrowUp" :search-input-placeholder="$t('msg.selectFieldToGroup')"
@keydown.enter.prevent="onClick(options[activeFieldIndex])" :options="options"
> toolbar-menu="groupBy"
<div class="flex pb-3 px-4 border-b-1 border-gray-100"> @selected="onClick"
<input ref="inputRef" v-model="search" class="w-full focus:outline-none" :placeholder="$t('msg.selectFieldToGroup')" /> />
</div>
<div class="flex-col w-full max-h-100 max-w-76 nc-scrollbar-md !overflow-y-auto">
<div v-if="!options.length" class="flex text-gray-500 px-4 py-2.25">{{ $t('general.empty') }}</div>
<div
v-for="(option, index) in options"
:key="index"
v-e="['c:group-by:add:column:select']"
class="flex flex-row h-10 items-center gap-x-1.5 px-2.5 hover:bg-gray-100 cursor-pointer nc-group-by-column-search-item"
:class="{
'bg-gray-100': activeFieldIndex === index,
}"
@click="onClick(option)"
>
<SmartsheetHeaderIcon :column="option" />
<NcTooltip class="truncate">
<template #title> {{ option.title }}</template>
<span>
{{ option.title }}
</span>
</NcTooltip>
</div>
</div>
</div> </div>
</template> </template>

74
packages/nc-gui/components/smartsheet/toolbar/CreateSort.vue

@ -11,12 +11,6 @@ const emits = defineEmits(['created'])
const { isParentOpen } = toRefs(props) const { isParentOpen } = toRefs(props)
const inputRef = ref()
const search = ref('')
const activeFieldIndex = ref(-1)
const activeView = inject(ActiveViewInj, ref()) const activeView = inject(ActiveViewInj, ref())
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
@ -55,72 +49,22 @@ const options = computed<ColumnType[]>(
/** ignore virtual fields which are system fields ( mm relation ) and qr code fields */ /** ignore virtual fields which are system fields ( mm relation ) and qr code fields */
} }
}) })
.filter((c: ColumnType) => !sorts.value.find((s) => s.fk_column_id === c.id)) .filter((c: ColumnType) => !sorts.value.find((s) => s.fk_column_id === c.id)) ?? [],
.filter((c: ColumnType) => c.title?.toLowerCase().includes(search.value.toLowerCase())) ?? [],
) )
const onClick = (column: ColumnType) => { const onClick = (column: ColumnType) => {
emits('created', column) emits('created', column)
} }
watch(
isParentOpen,
() => {
if (!isParentOpen.value) return
setTimeout(() => {
inputRef.value?.focus()
}, 100)
},
{
immediate: true,
},
)
onMounted(() => {
search.value = ''
activeFieldIndex.value = -1
})
const onArrowDown = () => {
activeFieldIndex.value = Math.min(activeFieldIndex.value + 1, options.value.length - 1)
}
const onArrowUp = () => {
activeFieldIndex.value = Math.max(activeFieldIndex.value - 1, 0)
}
</script> </script>
<template> <template>
<div <div class="nc-sort-create-modal">
class="flex flex-col w-full pt-4 pb-2 min-w-64 nc-sort-create-modal" <SmartsheetToolbarFieldListWithSearch
@keydown.arrow-down.prevent="onArrowDown" :is-parent-open="isParentOpen"
@keydown.arrow-up.prevent="onArrowUp" :search-input-placeholder="$t('msg.selectFieldToSort')"
@keydown.enter.prevent="onClick(options[activeFieldIndex])" :options="options"
> toolbar-menu="sort"
<div class="flex pb-3 px-4 border-b-1 border-gray-100"> @selected="onClick"
<input ref="inputRef" v-model="search" class="w-full focus:outline-none" :placeholder="$t('msg.selectFieldToSort')" /> />
</div>
<div class="flex-col w-full max-h-100 max-w-76 nc-scrollbar-md !overflow-y-auto">
<div v-if="!options.length" class="flex text-gray-500 px-4 py-2.25">{{ $t('general.empty') }}</div>
<div
v-for="(option, index) in options"
:key="index"
v-e="['c:sort:add:column:select']"
class="flex flex-row h-10 items-center gap-x-1.5 px-2.5 rounded-md m-1.5 hover:bg-gray-100 cursor-pointer nc-sort-column-search-item"
:class="{
'bg-gray-100': activeFieldIndex === index,
}"
@click="onClick(option)"
>
<SmartsheetHeaderIcon :column="option" />
<NcTooltip class="truncate" show-on-truncate-only>
<template #title> {{ option.title }}</template>
<template #default>
{{ option.title }}
</template>
</NcTooltip>
</div>
</div>
</div> </div>
</template> </template>

4
packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue

@ -94,8 +94,8 @@ if (!localValue.value && allowEmpty !== true) {
> >
<a-select-option v-for="option in options" :key="option.value" :label="option.label" :value="option.value"> <a-select-option v-for="option in options" :key="option.value" :label="option.label" :value="option.value">
<div class="flex items-center w-full justify-between w-full gap-2 max-w-50"> <div class="flex items-center w-full justify-between w-full gap-2 max-w-50">
<div class="flex gap-1 flex-1 items-center truncate items-center h-full"> <div class="flex gap-1.5 flex-1 items-center truncate items-center h-full">
<component :is="option.icon" class="min-w-5 !mx-0" /> <component :is="option.icon" class="!w-3.5 !h-3.5 !mx-0 !text-gray-500" />
<NcTooltip <NcTooltip
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }" :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
class="max-w-[15rem] truncate select-none" class="max-w-[15rem] truncate select-none"

184
packages/nc-gui/components/smartsheet/toolbar/FieldListWithSearch.vue

@ -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>

130
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -300,6 +300,19 @@ const showSystemField = computed({
}, },
}) })
const isDragging = ref<boolean>(false)
const fieldsMenuSearchRef = ref<HTMLInputElement>()
watch(open, (value) => {
if (!value) return
filterQuery.value = ''
setTimeout(() => {
fieldsMenuSearchRef.value?.focus()
}, 100)
})
useMenuCloseOnEsc(open) useMenuCloseOnEsc(open)
</script> </script>
@ -337,42 +350,60 @@ useMenuCloseOnEsc(open)
</div> </div>
<template #overlay> <template #overlay>
<div class="p-4 pr-0 bg-white w-90 rounded-2xl nc-table-toolbar-menu" data-testid="nc-fields-menu" @click.stop> <div
class="pt-2 bg-white w-full min-w-72 max-w-80 rounded-lg nc-table-toolbar-menu"
data-testid="nc-fields-menu"
@click.stop
>
<div <div
v-if="!filterQuery && !isPublic && (activeView?.type === ViewTypes.GALLERY || activeView?.type === ViewTypes.KANBAN)" v-if="!filterQuery && !isPublic && (activeView?.type === ViewTypes.GALLERY || activeView?.type === ViewTypes.KANBAN)"
class="flex flex-col gap-y-2 pr-4 mb-6" class="flex flex-col gap-y-2 px-2 mb-6"
> >
<div class="flex text-sm select-none">Select cover image field</div> <div class="flex text-sm select-none">Select cover image field</div>
<a-select <a-select
v-model:value="coverImageColumnId" v-model:value="coverImageColumnId"
:options="coverOptions" :options="coverOptions"
class="w-full" class="w-full"
dropdown-class-name="nc-dropdown-cover-image" dropdown-class-name="nc-dropdown-cover-image !rounded-lg"
@click.stop @click.stop
> >
<template #suffixIcon><GeneralIcon class="text-gray-700" icon="arrowDown" /></template> <template #suffixIcon><GeneralIcon class="text-gray-700" icon="arrowDown" /></template>
</a-select> </a-select>
</div> </div>
<div class="pr-4" @click.stop> <div class="px-2" @click.stop>
<a-input v-model:value="filterQuery" :placeholder="$t('placeholder.searchFields')" class="!rounded-lg"> <a-input
<template #prefix> <img class="h-3.5 w-3.5 mr-1" src="~/assets/nc-icons/search.svg" /> </template ref="fieldsMenuSearchRef"
v-model:value="filterQuery"
:placeholder="$t('placeholder.searchFields')"
class="nc-toolbar-dropdown-search-field-input"
>
<template #prefix> <GeneralIcon icon="search" class="nc-search-icon h-3.5 w-3.5 mr-1" /> </template
></a-input> ></a-input>
</div> </div>
<div v-if="!filterQuery" class="pr-4"> <div class="flex flex-col mt-2 pb-2 nc-scrollbar-thin max-h-[47vh] px-2">
<div class="pt-0.25 w-full bg-gray-50"></div>
</div>
<div class="flex flex-col my-1.5 nc-scrollbar-md max-h-[47.5vh] pr-3">
<div class="nc-fields-list"> <div class="nc-fields-list">
<div <div
v-if="!fields?.filter((el) => el.title.toLowerCase().includes(filterQuery.toLowerCase())).length" v-if="!fields?.filter((el) => el.title.toLowerCase().includes(filterQuery.toLowerCase())).length"
class="px-0.5 py-2 text-gray-500" class="px-2 py-6 text-gray-500 flex flex-col items-center gap-6 text-center"
> >
{{ $t('title.noFieldsFound') }} <img
src="~assets/img/placeholder/no-search-result-found.png"
class="!w-[164px] flex-none"
alt="No search results found"
/>
{{ $t('title.noResultsMatchedYourSearch') }}
</div> </div>
<Draggable v-model="fields" item-key="id" @change="onMove($event)"> <Draggable
v-model="fields"
item-key="id"
ghost-class="nc-fields-menu-items-ghost"
@change="onMove($event)"
@start="isDragging = true"
@end="isDragging = false"
>
<template #item="{ element: field }"> <template #item="{ element: field }">
<div <div
v-if=" v-if="
@ -382,13 +413,13 @@ useMenuCloseOnEsc(open)
" "
:key="field.id" :key="field.id"
:data-testid="`nc-fields-menu-${field.title}`" :data-testid="`nc-fields-menu-${field.title}`"
class="px-2 py-2 flex flex-row items-center first:border-t-1 border-b-1 border-x-1 first:rounded-t-lg last:rounded-b-lg border-gray-200" class="pl-2 flex flex-row items-center rounded-md hover:bg-gray-100"
@click.stop @click.stop
> >
<component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-600 mr-1" /> <component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-600 mr-1" />
<div <div
v-e="['a:fields:show-hide']" v-e="['a:fields:show-hide']"
class="flex flex-row items-center w-full truncate cursor-pointer ml-1" class="flex flex-row items-center w-full truncate cursor-pointer ml-1 py-[5px] pr-2"
@click=" @click="
() => { () => {
field.show = !field.show field.show = !field.show
@ -396,8 +427,8 @@ useMenuCloseOnEsc(open)
} }
" "
> >
<component :is="getIcon(metaColumnById[field.fk_column_id])" /> <component :is="getIcon(metaColumnById[field.fk_column_id])" class="!w-3.5 !h-3.5 !text-gray-500" />
<NcTooltip class="flex-1 px-1 truncate" show-on-truncate-only> <NcTooltip class="flex-1 pl-1 pr-2 truncate" show-on-truncate-only :disabled="isDragging">
<template #title> <template #title>
{{ field.title }} {{ field.title }}
</template> </template>
@ -438,54 +469,32 @@ useMenuCloseOnEsc(open)
<component :is="iconMap.underline" class="!w-3 !h-3" /> <component :is="iconMap.underline" class="!w-3 !h-3" />
</NcButton> </NcButton>
</div> </div>
<NcSwitch :checked="field.show" :disabled="field.isViewEssentialField" @change="$t('a:fields:show-hide')" /> <NcSwitch
:checked="field.show"
:disabled="field.isViewEssentialField"
size="xsmall"
@change="$t('a:fields:show-hide')"
/>
</div> </div>
<div class="flex-1" /> <div class="flex-1" />
</div> </div>
</template> </template>
<template v-if="activeView?.type === ViewTypes.GRID" #header>
<div
v-if="gridDisplayValueField && filteredFieldList[0].title.toLowerCase().includes(filterQuery.toLowerCase())"
:key="`pv-${gridDisplayValueField.id}`"
:class="{
'rounded-t-lg': filteredFieldList.length > 1,
'rounded-lg': filteredFieldList.length === 1,
}"
:data-testid="`nc-fields-menu-${gridDisplayValueField.title}`"
class="pl-7.4 pr-2 py-2 flex flex-row items-center border-1 border-gray-200"
@click.stop
>
<component :is="getIcon(metaColumnById[filteredFieldList[0].fk_column_id as string])" />
<NcTooltip class="px-1 flex-1 truncate" show-on-truncate-only>
<template #title>{{ filteredFieldList[0].title }}</template>
<template #default>{{ filteredFieldList[0].title }}</template>
</NcTooltip>
<NcSwitch :checked="true" :disabled="true" />
</div>
</template>
</Draggable> </Draggable>
</div> </div>
</div> </div>
<div class="flex pr-4 mt-1 gap-2"> <div v-if="!filterQuery" class="flex px-2 gap-2 py-2">
<NcButton <NcButton class="nc-fields-show-all-fields" size="small" type="ghost" @click="showAllColumns = !showAllColumns">
v-if="!filterQuery" {{ showAllColumns ? 'Hide all' : 'Show all' }} fields
class="nc-fields-show-all-fields !text-gray-500 !w-1/2"
size="small"
type="ghost"
@click="showAllColumns = !showAllColumns"
>
{{ showAllColumns ? $t('title.hideAll') : $t('general.showAll') }} {{ $t('objects.fields').toLowerCase() }}
</NcButton> </NcButton>
<NcButton <NcButton
v-if="!isPublic && !filterQuery" v-if="!isPublic"
class="nc-fields-show-system-fields !text-gray-500 !w-1/2" class="nc-fields-show-system-fields"
size="small" size="small"
type="ghost" type="ghost"
@click="showSystemField = !showSystemField" @click="showSystemField = !showSystemField"
> >
{{ showSystemField ? $t('title.hideSystemFields') : $t('activity.showSystemFields') }} {{ showSystemField ? 'Hide system fields' : 'Show system fields' }}
</NcButton> </NcButton>
</div> </div>
</div> </div>
@ -494,15 +503,16 @@ useMenuCloseOnEsc(open)
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
// :deep(.ant-checkbox-inner) {
// @apply transform scale-60;
// }
// :deep(.ant-checkbox) {
// @apply top-auto;
// }
:deep(.xxsmall) { :deep(.xxsmall) {
@apply !min-w-0; @apply !min-w-0;
} }
.nc-fields-menu-items-ghost {
@apply bg-gray-50;
}
.nc-fields-show-all-fields,
.nc-fields-show-system-fields {
@apply !text-xs !w-1/2 !text-gray-500 !border-none bg-gray-100 hover:(!text-gray-600 bg-gray-200);
}
</style> </style>

23
packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue

@ -251,19 +251,14 @@ watch(meta, async () => {
/> />
<div <div
v-else v-else
:class="{ ' min-w-[400px]': _groupBy.length }" class="flex flex-col bg-white overflow-auto nc-group-by-list menu-filter-dropdown max-h-[max(80vh,500px)] min-w-102 pt-2 pb-2 pl-4"
class="flex flex-col bg-white overflow-auto nc-group-by-list menu-filter-dropdown max-h-[max(80vh,500px)] py-6 pl-6"
data-testid="nc-group-by-menu" data-testid="nc-group-by-menu"
> >
<div <div class="group-by-grid max-h-100 nc-scrollbar-thing pr-4 py-2" @click.stop>
class="group-by-grid pb-1 max-h-100 nc-scrollbar-md pr-5"
:class="{ 'mb-2': availableColumns.length && fieldsToGroupBy.length > _groupBy.length && _groupBy.length < 3 }"
@click.stop
>
<template v-for="[i, group] of Object.entries(_groupBy)" :key="`grouped-by-${group.fk_column_id}`"> <template v-for="[i, group] of Object.entries(_groupBy)" :key="`grouped-by-${group.fk_column_id}`">
<LazySmartsheetToolbarFieldListAutoCompleteDropdown <LazySmartsheetToolbarFieldListAutoCompleteDropdown
v-model="group.fk_column_id" v-model="group.fk_column_id"
class="caption nc-sort-field-select" class="caption nc-sort-field-select w-44 flex flex-grow"
:columns="fieldsToGroupBy" :columns="fieldsToGroupBy"
:allow-empty="true" :allow-empty="true"
@change="saveGroupBy" @change="saveGroupBy"
@ -284,7 +279,7 @@ watch(meta, async () => {
:key="j" :key="j"
:value="option.value" :value="option.value"
> >
<div class="flex items-center justify-between gap-2"> <div class="w-full flex items-center justify-between gap-2">
<div class="truncate flex-1">{{ option.text }}</div> <div class="truncate flex-1">{{ option.text }}</div>
<component <component
:is="iconMap.check" :is="iconMap.check"
@ -296,10 +291,10 @@ watch(meta, async () => {
</a-select-option> </a-select-option>
</NcSelect> </NcSelect>
<a-tooltip placement="right" title="Remove"> <a-tooltip placement="right" title="Remove" class="flex-none min-w-40">
<NcButton <NcButton
v-e="['c:group-by:remove']" v-e="['c:group-by:remove']"
class="nc-group-by-item-remove-btn" class="nc-group-by-item-remove-btn min-w-40"
size="small" size="small"
type="text" type="text"
@click.stop="removeFieldFromGroupBy(i)" @click.stop="removeFieldFromGroupBy(i)"
@ -317,7 +312,7 @@ watch(meta, async () => {
> >
<NcButton <NcButton
v-e="['c:group-by:add']" v-e="['c:group-by:add']"
class="nc-add-group-by-btn !text-brand-500" class="nc-add-group-by-btn !text-brand-500 mt-1 mb-2"
style="width: fit-content" style="width: fit-content"
size="small" size="small"
type="text" type="text"
@ -346,7 +341,7 @@ watch(meta, async () => {
<style scoped> <style scoped>
.group-by-grid { .group-by-grid {
display: grid; display: grid;
grid-template-columns: auto 150px 22px; grid-template-columns: auto 150px auto;
@apply gap-[12px]; @apply gap-x-2 gap-y-3;
} }
</style> </style>

2
packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue

@ -94,7 +94,7 @@ useMenuCloseOnEsc(open)
</div> </div>
<template #overlay> <template #overlay>
<div <div
class="w-full bg-white shadow-lg p-1.5 menu-filter-dropdown border-1 border-gray-200 rounded-md overflow-hidden w-[160px]" class="w-full bg-white shadow-lg p-1.5 menu-filter-dropdown border-1 border-gray-200 rounded-lg overflow-hidden w-[160px]"
data-testid="nc-height-menu" data-testid="nc-height-menu"
> >
<div class="flex flex-col w-full text-sm" @click.stop> <div class="flex flex-col w-full text-sm" @click.stop>

106
packages/nc-gui/components/smartsheet/toolbar/SearchData.vue

@ -1,13 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { UITypes, isSystemColumn } from 'nocodb-sdk' import { UITypes, isSystemColumn } from 'nocodb-sdk'
import type { TableType } from 'nocodb-sdk' import type { ColumnType, TableType } from 'nocodb-sdk'
import { import {
ActiveViewInj, ActiveViewInj,
ReloadViewDataHookInj, ReloadViewDataHookInj,
computed, computed,
iconMap, iconMap,
inject, inject,
onClickOutside,
ref, ref,
useFieldQuery, useFieldQuery,
useSmartsheetStoreOrThrow, useSmartsheetStoreOrThrow,
@ -25,21 +24,8 @@ const isDropdownOpen = ref(false)
const { isMobileMode } = useGlobal() const { isMobileMode } = useGlobal()
const isFocused = ref(false) const columns = computed(
() => (meta.value as TableType)?.columns?.filter((column) => !isSystemColumn(column) && column?.uidt !== UITypes.Links) ?? [],
const searchDropdown = ref(null)
onClickOutside(searchDropdown, () => (isDropdownOpen.value = false))
const columns = computed(() =>
(meta.value as TableType)?.columns
?.filter((column) => !isSystemColumn(column) && column?.uidt !== UITypes.Links)
?.map((column) => ({
value: column.id,
label: column.title,
column,
primaryValue: column.pv,
})),
) )
watch( watch(
@ -59,10 +45,12 @@ function onPressEnter() {
const displayColumnLabel = computed(() => { const displayColumnLabel = computed(() => {
if (search.value.field) { if (search.value.field) {
// use search field label if specified // use search field label if specified
return columns.value?.find((column) => column.value === search.value.field)?.label return columns.value?.find((column) => column.id === search.value.field)?.title
} }
// use primary value label by default // use primary value label by default
return columns.value?.find((column) => column.primaryValue)?.label const pvColumn = columns.value?.find((column) => column.pv)
search.value.field = pvColumn?.id as string
return pvColumn?.title
}) })
watchDebounced( watchDebounced(
@ -76,58 +64,46 @@ watchDebounced(
}, },
) )
watch(columns, () => { const onSelectOption = (column: ColumnType) => {
if (columns.value?.length) { search.value.field = column.id as string
search.value.field = columns.value[0].value as string isDropdownOpen.value = false
} }
})
</script> </script>
<template> <template>
<div <div
class="flex flex-row border-1 rounded-lg h-8 xs:(h-10 ml-0) ml-1 border-gray-200 overflow-hidden" class="flex flex-row border-1 rounded-lg h-8 xs:(h-10 ml-0) ml-1 border-gray-200 overflow-hidden focus-within:border-primary"
:class="{ 'border-primary': search.query.length !== 0 || isFocused }" :class="{ 'border-primary': search.query.length !== 0 }"
> >
<div <NcDropdown
ref="searchDropdown" v-model:visible="isDropdownOpen"
class="flex items-center group relative px-2 cursor-pointer border-r-1 border-gray-200 hover:bg-gray-100" :trigger="['click']"
:class="{ 'bg-gray-50 ': isDropdownOpen }" overlay-class-name="nc-dropdown-toolbar-search-field-option"
@click="isDropdownOpen = !isDropdownOpen"
> >
<GeneralIcon icon="search" class="ml-1 mr-2 h-3.5 w-3.5 text-gray-500 group-hover:text-black" /> <div
<div v-if="!isMobileMode" class="w-16 text-[0.75rem] font-medium text-gray-400 truncate"> class="flex items-center group px-2 cursor-pointer border-r-1 border-gray-200 hover:bg-gray-100"
{{ displayColumnLabel }} :class="{ 'bg-gray-50 ': isDropdownOpen }"
</div> @click="isDropdownOpen = !isDropdownOpen"
<div class="xs:(text-gray-600) group-hover:text-gray-700 sm:(text-gray-400)">
<component :is="iconMap.arrowDown" class="text-sm text-inherit" />
</div>
<a-select
v-model:value="search.field"
v-e="['c:search:field:select:open']"
:open="isDropdownOpen"
size="small"
:dropdown-match-select-width="false"
dropdown-class-name="!rounded-lg nc-dropdown-toolbar-search-field-option max-w-64"
class="py-1 !absolute top-2 left-0 w-full h-full z-10 text-xs opacity-0"
@change="onPressEnter"
> >
<a-select-option v-for="op of columns" :key="op.value" v-e="['c:search:field:select']" :value="op.value"> <GeneralIcon icon="search" class="ml-1 mr-2 h-3.5 w-3.5 text-gray-500 group-hover:text-black" />
<div class="text-sm flex items-center gap-2"> <div v-if="!isMobileMode" class="w-16 text-xs font-medium text-gray-400 truncate">
<SmartsheetHeaderIcon :column="op.column" /> {{ displayColumnLabel }}
<NcTooltip class="truncate flex-1" placement="top" show-on-truncate-only> </div>
<template #title>{{ op.label }}</template> <div class="xs:(text-gray-600) group-hover:text-gray-700 sm:(text-gray-400)">
{{ op.label }} <component :is="iconMap.arrowDown" class="text-sm text-inherit" />
</NcTooltip> </div>
<component </div>
:is="iconMap.check" <template #overlay>
v-if="search.field === op.value" <SmartsheetToolbarFieldListWithSearch
id="nc-selected-item-icon" :is-parent-open="isDropdownOpen"
class="text-primary w-4 h-4" :selected-option-id="search.field"
/> show-selected-option
</div> :options="columns"
</a-select-option> toolbar-menu="globalSearch"
</a-select> @selected="onSelectOption"
</div> />
</template>
</NcDropdown>
<a-input <a-input
v-model:value="search.query" v-model:value="search.query"
@ -137,8 +113,6 @@ watch(columns, () => {
:bordered="false" :bordered="false"
data-testid="search-data-input" data-testid="search-data-input"
@press-enter="onPressEnter" @press-enter="onPressEnter"
@focus="isFocused = true"
@blur="isFocused = false"
> >
</a-input> </a-input>
</div> </div>

24
packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue

@ -139,13 +139,8 @@ onMounted(() => {
</div> </div>
<template #overlay> <template #overlay>
<SmartsheetToolbarCreateSort v-if="!sorts.length" :is-parent-open="open" @created="addSort" /> <SmartsheetToolbarCreateSort v-if="!sorts.length" :is-parent-open="open" @created="addSort" />
<div <div v-else class="pt-2 pb-2 pl-4 nc-filter-list max-h-[max(80vh,30rem)] min-w-102" data-testid="nc-sorts-menu">
v-else <div class="sort-grid max-h-120 nc-scrollbar-thin pr-4 my-2 py-1" @click.stop>
:class="{ 'min-w-102': sorts.length }"
class="py-6 pl-6 nc-filter-list max-h-[max(80vh,30rem)]"
data-testid="nc-sorts-menu"
>
<div class="sort-grid max-h-120 nc-scrollbar-md" :class="{ 'pr-3.5': sorts?.length }" @click.stop>
<template v-for="(sort, i) of sorts" :key="i"> <template v-for="(sort, i) of sorts" :key="i">
<SmartsheetToolbarFieldListAutoCompleteDropdown <SmartsheetToolbarFieldListAutoCompleteDropdown
v-model="sort.fk_column_id" v-model="sort.fk_column_id"
@ -160,7 +155,7 @@ onMounted(() => {
v-model:value="sort.direction" v-model:value="sort.direction"
class="shrink grow-0 nc-sort-dir-select" class="shrink grow-0 nc-sort-dir-select"
:label="$t('labels.operation')" :label="$t('labels.operation')"
dropdown-class-name="sort-dir-dropdown nc-dropdown-sort-dir" dropdown-class-name="sort-dir-dropdown nc-dropdown-sort-dir !rounded-lg"
@click.stop @click.stop
@select="saveOrUpdate(sort, i)" @select="saveOrUpdate(sort, i)"
> >
@ -170,7 +165,7 @@ onMounted(() => {
v-e="['c:sort:operation:select']" v-e="['c:sort:operation:select']"
:value="option.value" :value="option.value"
> >
<div class="flex items-center justify-between gap-2"> <div class="w-full flex items-center justify-between gap-2">
<div class="truncate flex-1">{{ option.text }}</div> <div class="truncate flex-1">{{ option.text }}</div>
<component <component
:is="iconMap.check" :is="iconMap.check"
@ -198,14 +193,13 @@ onMounted(() => {
v-if="availableColumns.length" v-if="availableColumns.length"
v-model:visible="showCreateSort" v-model:visible="showCreateSort"
:trigger="['click']" :trigger="['click']"
class="mt-3"
overlay-class-name="nc-toolbar-dropdown" overlay-class-name="nc-toolbar-dropdown"
> >
<template v-if="isEeUI && !isPublic"> <template v-if="isEeUI && !isPublic">
<NcButton <NcButton
v-if="sorts.length < getPlanLimit(PlanLimitTypes.SORT_LIMIT)" v-if="sorts.length < getPlanLimit(PlanLimitTypes.SORT_LIMIT)"
v-e="['c:sort:add']" v-e="['c:sort:add']"
class="!text-brand-500" class="!text-brand-500 mt-1 mb-2"
type="text" type="text"
size="small" size="small"
@click.stop="showCreateSort = true" @click.stop="showCreateSort = true"
@ -219,7 +213,13 @@ onMounted(() => {
<span v-else></span> <span v-else></span>
</template> </template>
<template v-else> <template v-else>
<NcButton v-e="['c:sort:add']" class="!text-brand-500" type="text" size="small" @click.stop="showCreateSort = true"> <NcButton
v-e="['c:sort:add']"
class="!text-brand-500 mt-1 mb-2"
type="text"
size="small"
@click.stop="showCreateSort = true"
>
<div class="flex gap-1 items-center"> <div class="flex gap-1 items-center">
<component :is="iconMap.plus" /> <component :is="iconMap.plus" />
<!-- Add Sort Option --> <!-- Add Sort Option -->

5
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue

@ -153,7 +153,10 @@ const onDelete = async () => {
> >
<NcTooltip> <NcTooltip>
<template #title> {{ $t('labels.clickToCopyViewID') }} </template> <template #title> {{ $t('labels.clickToCopyViewID') }} </template>
<div class="flex items-center justify-between p-2 mx-1.5 rounded-md cursor-pointer hover:bg-gray-100 group" @click="onViewIdCopy"> <div
class="flex items-center justify-between p-2 mx-1.5 rounded-md cursor-pointer hover:bg-gray-100 group"
@click="onViewIdCopy"
>
<div class="flex text-xs font-bold text-gray-500 ml-1"> <div class="flex text-xs font-bold text-gray-500 ml-1">
{{ {{
$t('labels.viewIdColon', { $t('labels.viewIdColon', {

7
packages/nc-gui/composables/useViewColumns.ts

@ -220,7 +220,12 @@ const [useProvideViewColumns, useViewColumns] = useInjectionState(
const filteredFieldList = computed(() => { const filteredFieldList = computed(() => {
return ( return (
fields.value?.filter((field: Field) => { fields.value?.filter((field: Field) => {
if (metaColumnById?.value?.[field.fk_column_id!]?.pv) return true if (
metaColumnById?.value?.[field.fk_column_id!]?.pv &&
(!filterQuery.value || field.title.toLowerCase().includes(filterQuery.value.toLowerCase()))
) {
return true
}
// hide system columns if not enabled // hide system columns if not enabled
if (!showSystemFields.value && isSystemColumn(metaColumnById?.value?.[field.fk_column_id!])) { if (!showSystemFields.value && isSystemColumn(metaColumnById?.value?.[field.fk_column_id!])) {

3
packages/nc-gui/lang/en.json

@ -433,7 +433,8 @@
}, },
"selectFieldsFromRightPannelToAddHere": "Select fields from right panel to add here", "selectFieldsFromRightPannelToAddHere": "Select fields from right panel to add here",
"noOptionsFound": "No options found", "noOptionsFound": "No options found",
"surveyFormSubmitConfirmMsg": "Are you sure you want to submit this form?" "surveyFormSubmitConfirmMsg": "Are you sure you want to submit this form?",
"noResultsMatchedYourSearch": "Your search did not yield any matching results."
}, },
"labels": { "labels": {
"selectYear": "Select Year", "selectYear": "Select Year",

2
packages/nc-gui/windi.config.ts

@ -46,6 +46,8 @@ export default defineConfig({
'color-transition': 'transition-colors duration-100 ease-in', 'color-transition': 'transition-colors duration-100 ease-in',
'scrollbar-thin-primary': 'scrollbar scrollbar-thin scrollbar-thumb-rounded scrollbar-thumb-primary scrollbar-track-white', 'scrollbar-thin-primary': 'scrollbar scrollbar-thin scrollbar-thumb-rounded scrollbar-thumb-primary scrollbar-track-white',
'scrollbar-thin-dull': 'scrollbar scrollbar-thin scrollbar-thumb-rounded-md scrollbar-thumb-gray-100 scrollbar-track-white', 'scrollbar-thin-dull': 'scrollbar scrollbar-thin scrollbar-thumb-rounded-md scrollbar-thumb-gray-100 scrollbar-track-white',
'nc-scrollbar-thin':
'scrollbar scrollbar-thin scrollbar-thumb-gray-200 hover:scrollbar-thumb-gray-300 scrollbar-track-transparent',
}, },
theme: { theme: {

4
tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts

@ -93,14 +93,14 @@ export class ToolbarGroupByPage extends BasePage {
await this.rootPage await this.rootPage
.locator('.nc-group-by-create-modal') .locator('.nc-group-by-create-modal')
.locator('.nc-group-by-column-search-item >> div', { hasText: regexTitle }) .locator('.nc-group-by-column-search-item', { hasText: regexTitle })
.scrollIntoViewIfNeeded(); .scrollIntoViewIfNeeded();
// select column // select column
const selectColumn = async () => const selectColumn = async () =>
await this.rootPage await this.rootPage
.locator('.nc-group-by-create-modal') .locator('.nc-group-by-create-modal')
.locator('.nc-group-by-column-search-item >> div', { hasText: regexTitle }) .locator('.nc-group-by-column-search-item', { hasText: regexTitle })
.click({ force: true }); .click({ force: true });
await this.waitForResponse({ await this.waitForResponse({

Loading…
Cancel
Save