Browse Source

Merge pull request #7181 from nocodb/fix/dropdown-ui

Fix/dropdown UI
pull/7222/head
Raju Udava 9 months ago committed by GitHub
parent
commit
7d7267a905
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      packages/nc-gui/assets/style.scss
  2. 21
      packages/nc-gui/components/account/UserList.vue
  3. 27
      packages/nc-gui/components/account/UsersModal.vue
  4. 2
      packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue
  5. 48
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  6. 14
      packages/nc-gui/components/general/language/Menu.vue
  7. 4
      packages/nc-gui/components/general/language/index.vue
  8. 5
      packages/nc-gui/components/nc/Select.vue
  9. 76
      packages/nc-gui/components/roles/Selector.vue
  10. 16
      packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue
  11. 32
      packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue
  12. 24
      packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue
  13. 19
      packages/nc-gui/components/smartsheet/column/DateOptions.vue
  14. 20
      packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue
  15. 12
      packages/nc-gui/components/smartsheet/column/DecimalOptions.vue
  16. 14
      packages/nc-gui/components/smartsheet/column/DurationOptions.vue
  17. 20
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  18. 41
      packages/nc-gui/components/smartsheet/column/LookupOptions.vue
  19. 19
      packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue
  20. 43
      packages/nc-gui/components/smartsheet/column/RatingOptions.vue
  21. 52
      packages/nc-gui/components/smartsheet/column/RollupOptions.vue
  22. 10
      packages/nc-gui/components/smartsheet/details/Api.vue
  23. 6
      packages/nc-gui/components/smartsheet/details/Webhooks.vue
  24. 26
      packages/nc-gui/components/smartsheet/grid/Table.vue
  25. 74
      packages/nc-gui/components/smartsheet/header/Menu.vue
  26. 50
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  27. 2
      packages/nc-gui/components/smartsheet/toolbar/CreateSort.vue
  28. 32
      packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue
  29. 10
      packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue
  30. 26
      packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue
  31. 26
      packages/nc-gui/components/smartsheet/toolbar/SearchData.vue
  32. 10
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  33. 30
      packages/nc-gui/components/webhook/Editor.vue
  34. 6
      packages/nc-gui/utils/virtualCell.ts
  35. 9
      tests/playwright/pages/Dashboard/Grid/Column/index.ts
  36. 8
      tests/playwright/pages/Dashboard/WebhookForm/index.ts
  37. 2
      tests/playwright/tests/db/features/keyboardShortcuts.spec.ts

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

@ -36,6 +36,9 @@ body {
overflow-y: visible !important;
}
.rc-virtual-list-holder-inner {
@apply !px-1.5
}
.ant-layout-header {
height: var(--topbar-height) !important;
}
@ -445,7 +448,7 @@ a {
}
.ant-dropdown-menu {
@apply !p-0 !rounded;
@apply !p-0 !rounded-lg;
}
.ant-tabs-dropdown-menu-title-content {
@ -505,8 +508,18 @@ a {
@apply p-4;
}
.ant-select-item-option-selected:not(.ant-select-item-option-disabled):hover,
.ant-select-item-option-active:not(.ant-select-item-option-disabled) {
@apply bg-primary bg-opacity-20;
@apply bg-gray-300 bg-opacity-20;
}
/* Hide the element with id nc-selected-item-icon */
.ant-select-selection-item #nc-selected-item-icon {
@apply hidden;
}
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
@apply bg-transparent;
}
.ant-select-selection-search-input:focus {
@ -591,7 +604,7 @@ a {
}
.ant-popover-inner {
padding: 0 !important;
@apply rounded-lg !p-0;
}
.ant-popover-inner-content {
@apply !px-1.5 !py-1 text-xs;

21
packages/nc-gui/components/account/UserList.vue

@ -215,6 +215,7 @@ const openDeleteModal = (user: UserType) => {
v-model:value="el.roles"
class="w-55 nc-user-roles"
:dropdown-match-select-width="false"
dropdown-class-name="max-w-64"
@change="updateRole(el.id, el.roles as string)"
>
<a-select-option
@ -222,7 +223,15 @@ const openDeleteModal = (user: UserType) => {
:value="OrgUserRoles.CREATOR"
:label="$t(`objects.roleType.orgLevelCreator`)"
>
<div data-rec="true">{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<div class="flex items-center gap-1 justify-between">
<div data-rec="true">{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<GeneralIcon
v-if="el?.roles === OrgUserRoles.CREATOR"
id="nc-selected-item-icon"
icon="check"
class="w-4 h-4 text-primary"
/>
</div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgCreator') }}
</span>
@ -233,7 +242,15 @@ const openDeleteModal = (user: UserType) => {
:value="OrgUserRoles.VIEWER"
:label="$t(`objects.roleType.orgLevelViewer`)"
>
<div data-rec="true">{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<div class="flex items-center gap-1 justify-between">
<div data-rec="true">{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<GeneralIcon
v-if="el.roles === OrgUserRoles.VIEWER"
id="nc-selected-item-icon"
icon="check"
class="w-4 h-4 text-primary"
/>
</div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgViewer') }}
</span>

27
packages/nc-gui/components/account/UsersModal.vue

@ -193,13 +193,26 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<a-form-item name="role" :rules="[{ required: true, message: $t('msg.roleRequired') }]">
<div class="ml-1 mb-1 text-xs text-gray-500">{{ $t('labels.selectUserRole') }}</div>
<a-select v-model:value="usersData.role" class="nc-user-roles" dropdown-class-name="nc-dropdown-user-role">
<a-select
v-model:value="usersData.role"
class="nc-user-roles"
dropdown-class-name="nc-dropdown-user-role !px-2"
>
<a-select-option
class="nc-role-option"
:value="OrgUserRoles.CREATOR"
:label="$t(`objects.roleType.orgLevelCreator`)"
>
<div data-rec="true">{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<div class="flex items-center gap-1 justify-between">
<div data-rec="true">{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<GeneralIcon
v-if="usersData.role === OrgUserRoles.CREATOR"
id="nc-selected-item-icon"
icon="check"
class="w-4 h-4 text-primary"
/>
</div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgCreator') }}
</span>
@ -210,7 +223,15 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
:value="OrgUserRoles.VIEWER"
:label="$t(`objects.roleType.orgLevelViewer`)"
>
<div data-rec="true">{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<div class="flex items-center gap-1 justify-between">
<div data-rec="true">{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<GeneralIcon
v-if="usersData.role === OrgUserRoles.VIEWER"
id="nc-selected-item-icon"
icon="check"
class="w-4 h-4 text-primary"
/>
</div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgViewer') }}
</span>

2
packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue

@ -157,7 +157,7 @@ onMounted(() => {
</NcMenuItem>
<template #content>
<div class="bg-white max-h-50vh scrollbar-thin-dull min-w-50 !overflow-auto">
<div class="bg-white max-h-50vh scrollbar-thin-dull min-w-64 !overflow-auto">
<LazyGeneralLanguageMenu />
</div>
</template>

48
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

@ -454,8 +454,16 @@ const toggleModal = (val: boolean) => {
dropdown-class-name="nc-dropdown-ext-db-type"
@change="onClientChange"
>
<a-select-option v-for="client in clientTypes" :key="client.value" :value="client.value"
>{{ client.text }}
<a-select-option v-for="client in clientTypes" :key="client.value" :value="client.value">
<div class="flex items-center gap-2 justify-between">
<div>{{ client.text }}</div>
<component
:is="iconMap.check"
v-if="formState.dataSource.client === client.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>
@ -535,7 +543,17 @@ const toggleModal = (val: boolean) => {
dropdown-class-name="nc-dropdown-ssl-mode"
@select="onSSLModeChange"
>
<a-select-option v-for="opt in Object.values(SSLUsage)" :key="opt" :value="opt">{{ opt }} </a-select-option>
<a-select-option v-for="opt in Object.values(SSLUsage)" :key="opt" :value="opt">
<div class="flex items-center gap-2 justify-between">
<div>{{ opt }}</div>
<component
:is="iconMap.check"
v-if="formState?.sslUse === opt"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>
@ -619,7 +637,17 @@ const toggleModal = (val: boolean) => {
v-model:value="formState.inflection.inflectionTable"
dropdown-class-name="nc-dropdown-inflection-table-name"
>
<a-select-option v-for="tp in inflectionTypes" :key="tp" :value="tp">{{ tp }} </a-select-option>
<a-select-option v-for="tp in inflectionTypes" :key="tp" :value="tp">
<div class="flex items-center gap-2 justify-between">
<div>{{ tp }}</div>
<component
:is="iconMap.check"
v-if="formState?.inflection?.inflectionTable === tp"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>
@ -628,7 +656,17 @@ const toggleModal = (val: boolean) => {
v-model:value="formState.inflection.inflectionColumn"
dropdown-class-name="nc-dropdown-inflection-column-name"
>
<a-select-option v-for="tp in inflectionTypes" :key="tp" :value="tp">{{ tp }} </a-select-option>
<a-select-option v-for="tp in inflectionTypes" :key="tp" :value="tp"
><div class="flex items-center gap-2 justify-between">
<div>{{ tp }}</div>
<component
:is="iconMap.check"
v-if="formState?.inflection?.inflectionColumn === tp"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>

14
packages/nc-gui/components/general/language/Menu.vue

@ -21,11 +21,11 @@ async function changeLanguage(lang: string) {
</script>
<template>
<a-menu-item class="mt-1 group">
<a-menu-item class="group rounded-md !my-0.5">
<a
href="https://docs.nocodb.com/engineering/translation/#how-to-contribute--for-community-members"
target="_blank"
class="caption nc-base-menu-item py-2 text-primary underline hover:opacity-75"
class="caption nc-base-menu-item rounded-md underline hover:!text-primary"
rel="noopener"
>
{{ $t('activity.translate') }}
@ -35,13 +35,15 @@ async function changeLanguage(lang: string) {
<a-menu-item
v-for="[key, lang] of languages"
:key="key"
:class="key === locale ? '!bg-primary bg-opacity-10 text-primary' : ''"
class="group"
class="group rounded-md !my-0.5"
:value="key"
@click="changeLanguage(key)"
>
<div :class="key === locale ? '!font-semibold !text-primary' : ''" class="nc-base-menu-item capitalize">
{{ Language[key] || lang }}
<div class="flex items-center gap-2 justify-between">
<div class="nc-base-menu-item w-fit capitalize">
{{ Language[key] || lang }}
</div>
<component :is="iconMap.check" v-if="key === locale" class="text-primary w-4 h-4" />
</div>
</a-menu-item>
</template>

4
packages/nc-gui/components/general/language/index.vue

@ -9,8 +9,8 @@
</div>
<template #overlay>
<a-menu class="nc-scrollbar-dark-md min-w-50 max-h-90vh overflow-auto !p-1 m-1 rounded-md border-1 border-gray-200">
<GeneralLanguageMenu />
<a-menu class="nc-scrollbar-dark-md min-w-64 max-h-90vh overflow-auto !p-1 m-1 rounded-md border-1 border-gray-200">
<div class="m-1.5"><GeneralLanguageMenu /></div>
</a-menu>
</template>
</a-dropdown>

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

@ -62,7 +62,7 @@ const onChange = (value: string) => {
<style lang="scss">
.ant-select-item {
@apply !xs:h-13;
@apply !xs:h-13 !min-h-[2.375rem] !p-2;
}
.ant-select-item-option-content {
@apply !xs:mt-2.5;
@ -70,6 +70,9 @@ const onChange = (value: string) => {
.ant-select-item-option-state {
@apply !xs:mt-1.75;
}
.ant-select-item-option {
@apply !rounded-md;
}
.nc-select.ant-select {
height: fit-content;

76
packages/nc-gui/components/roles/Selector.vue

@ -1,6 +1,7 @@
<script lang="ts" setup>
import { RoleDescriptions } from 'nocodb-sdk'
import type { RoleLabels } from 'nocodb-sdk'
import type { SelectValue } from 'ant-design-vue/es/select'
import { toRef } from '#imports'
const props = withDefaults(
@ -21,47 +22,50 @@ const props = withDefaults(
const roleRef = toRef(props, 'role')
const inheritRef = toRef(props, 'inherit')
const descriptionRef = toRef(props, 'description')
const isDropdownOpen = ref(false)
const dropdownRef = ref(null)
const sizeRef = toRef(props, 'size')
onClickOutside(dropdownRef, () => (isDropdownOpen.value = false))
function onChangeRole(val: SelectValue) {
props.onRoleChange(val as keyof typeof RoleLabels)
isDropdownOpen.value = false
}
</script>
<template>
<NcDropdown size="lg" class="nc-roles-selector">
<RolesBadge data-testid="roles" :role="roleRef" :inherit="inheritRef === role" clickable :size="sizeRef" />
<template #overlay>
<div class="nc-role-select-dropdown flex flex-col gap-1 p-2">
<div class="flex flex-col gap-1">
<div
v-for="rl in props.roles"
:key="rl"
v-e="['c:workspace:settings:user-role-change']"
:value="rl"
:selected="rl === roleRef"
@click="props.onRoleChange(rl)"
>
<div
class="flex flex-col py-1.5 rounded-lg px-2 gap-1 bg-transparent cursor-pointer hover:bg-gray-100"
:class="{
'w-[350px]': descriptionRef,
'w-[200px]': !descriptionRef,
}"
>
<div class="flex items-center justify-between">
<RolesBadge
class="!bg-white hover:!bg-gray-100"
:class="`nc-role-select-${rl}`"
:role="rl"
:inherit="inheritRef === rl"
:border="false"
/>
<GeneralIcon v-if="rl === roleRef" icon="check" />
</div>
<div v-if="descriptionRef" class="text-gray-500">{{ RoleDescriptions[rl] }}</div>
</div>
<div ref="dropdownRef" size="lg" class="nc-roles-selector relative" @click="isDropdownOpen = !isDropdownOpen">
<RolesBadge data-testid="roles" :role="roleRef" :inherit="inheritRef === role" :size="sizeRef" />
<a-select
v-model:value="roleRef"
:open="isDropdownOpen"
:dropdown-match-select-width="false"
dropdown-class-name="!rounded-lg !h-fit max-w-64"
class="py-1 !absolute top-0 left-0 w-full h-full z-10 text-xs opacity-0"
@change="onChangeRole"
>
<a-select-option v-for="rl in props.roles" :key="rl" v-e="['c:workspace:settings:user-role-change']" :value="rl">
<div
:class="{
'w-[350px]': descriptionRef,
'w-[200px]': !descriptionRef,
}"
class="flex flex-col nc-role-select-dropdown gap-1"
>
<div class="flex items-center justify-between">
<RolesBadge :class="`nc-role-select-${rl}`" :role="rl" :inherit="inheritRef === rl" :border="false" />
<GeneralIcon v-if="rl === roleRef" icon="check" class="text-primary" />
</div>
<div v-if="descriptionRef" class="text-gray-500">{{ RoleDescriptions[rl] }}</div>
</div>
</div>
</template>
</NcDropdown>
</a-select-option>
</a-select>
</div>
</template>
<style scoped lang="scss"></style>
<style lang="scss">
.ant-select-item-option-content {
white-space: normal; /* Change from 'nowrap' to 'normal' */
}
</style>

16
packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue

@ -73,9 +73,12 @@ vModel.value.au = !!vModel.value.au */
</div>
<a-form-item :label="$t('labels.databaseType')" v-bind="validateInfos.dt">
<a-select v-model:value="vModel.dt" dropdown-class-name="nc-dropdown-db-type " @change="onDataTypeChange">
<a-select v-model:value="vModel.dt" dropdown-class-name="nc-dropdown-db-type " class="!mt-0.5" @change="onDataTypeChange">
<a-select-option v-for="type in dataTypes" :key="type" :value="type">
{{ type }}
<div class="flex gap-2 items-center justify-between">
{{ type }}
<component :is="iconMap.check" v-if="vModel.dt === type" id="nc-selected-item-icon" class="text-primary w-4 h-4" />
</div>
</a-select-option>
</a-select>
</a-form-item>
@ -83,14 +86,19 @@ vModel.value.au = !!vModel.value.au */
<a-form-item v-if="!hideLength" :label="$t('labels.lengthValue')">
<a-input
v-model:value="vModel.dtxp"
class="!rounded-md"
class="!rounded-md !mt-0.5"
:disabled="sqlUi.getDefaultLengthIsDisabled(vModel.dt) || !sqlUi.columnEditable(vModel)"
@input="onAlter"
/>
</a-form-item>
<a-form-item v-if="sqlUi.showScale(vModel)" label="Scale">
<a-input v-model:value="vModel.dtxs" class="!rounded-md" :disabled="!sqlUi.columnEditable(vModel)" @input="onAlter" />
<a-input
v-model:value="vModel.dtxs"
class="!rounded-md !mt-0.5"
:disabled="!sqlUi.columnEditable(vModel)"
@input="onAlter"
/>
</a-form-item>
<LazySmartsheetColumnPgBinaryOptions v-if="isPg(meta?.source_id) && vModel.dt === 'bytea'" v-model:value="vModel" />

32
packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue

@ -80,20 +80,28 @@ watch(
<a-form-item label="Icon">
<a-select v-model:value="vModel.meta.iconIdx" class="w-52" dropdown-class-name="nc-dropdown-checkbox-icon">
<a-select-option v-for="(icon, i) of iconList" :key="i" :value="i">
<div class="flex items-center">
<component
:is="getMdiIcon(icon.checked)"
class="mx-1"
:style="{
color: vModel.meta.color,
}"
/>
<div class="flex gap-2 w-full truncate items-center">
<div class="flex-1 truncate">
<component
:is="getMdiIcon(icon.checked)"
class="mx-1"
:style="{
color: vModel.meta.color,
}"
/>
<component
:is="getMdiIcon(icon.unchecked)"
:style="{
color: vModel.meta.color,
}"
/>
</div>
<component
:is="getMdiIcon(icon.unchecked)"
:style="{
color: vModel.meta.color,
}"
:is="iconMap.check"
v-if="vModel.meta.iconIdx === i"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>

24
packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue

@ -89,7 +89,19 @@ currencyLocales().then((locales) => {
dropdown-class-name="nc-dropdown-currency-cell-locale"
>
<a-select-option v-for="(currencyLocale, i) of currencyLocaleList" :key="i" :value="currencyLocale.value">
{{ currencyLocale.text }}
<div class="flex gap-2 w-full truncate items-center">
<NcTooltip show-on-truncate-only class="flex-1 truncate">
<template #title>{{ currencyLocale.text }}</template>
{{ currencyLocale.text }}
</NcTooltip>
<component
:is="iconMap.check"
v-if="vModel.meta.currency_locale === currencyLocale.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>
@ -106,7 +118,15 @@ currencyLocales().then((locales) => {
dropdown-class-name="nc-dropdown-currency-cell-code"
>
<a-select-option v-for="(currencyCode, i) of currencyList" :key="i" :value="currencyCode">
{{ currencyCode }}
<div class="flex gap-2 w-full justify-between items-center">
{{ currencyCode }}
<component
:is="iconMap.check"
v-if="vModel.meta.currency_code === currencyCode"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>

19
packages/nc-gui/components/smartsheet/column/DateOptions.vue

@ -18,12 +18,21 @@ if (!vModel.value.meta?.date_format) {
<template>
<a-form-item :label="$t('labels.dateFormat')">
<a-select v-model:value="vModel.meta.date_format" class="nc-date-select" dropdown-class-name="nc-dropdown-date-format">
<a-select
v-model:value="vModel.meta.date_format"
show-search
class="nc-date-select"
dropdown-class-name="nc-dropdown-date-format"
>
<a-select-option v-for="(format, i) of [...dateFormats, ...dateMonthFormats]" :key="i" :value="format">
<div class="flex flex-row items-center">
<div class="text-xs">
{{ format }}
</div>
<div class="flex gap-2 justify-between items-center">
{{ format }}
<component
:is="iconMap.check"
v-if="vModel.meta.date_format === format"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>

20
packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue

@ -25,14 +25,30 @@ if (!vModel.value.meta?.time_format) {
<a-form-item :label="$t('labels.dateFormat')">
<a-select v-model:value="vModel.meta.date_format" class="nc-date-select" dropdown-class-name="nc-dropdown-date-format">
<a-select-option v-for="(format, i) of dateFormats" :key="i" :value="format">
{{ format }}
<div class="flex gap-2 w-full justify-between items-center">
{{ format }}
<component
:is="iconMap.check"
v-if="vModel.meta.date_format === format"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('labels.timeFormat')">
<a-select v-model:value="vModel.meta.time_format" class="nc-time-select" dropdown-class-name="nc-dropdown-time-format">
<a-select-option v-for="(format, i) of timeFormats" :key="i" :value="format">
{{ format }}
<div class="flex gap-2 w-full justify-between items-center" :data-testid="`nc-time-${format}`">
{{ format }}
<component
:is="iconMap.check"
v-if="vModel.meta.time_format === format"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>

12
packages/nc-gui/components/smartsheet/column/DecimalOptions.vue

@ -40,10 +40,14 @@ onMounted(() => {
dropdown-class-name="nc-dropdown-decimal-format"
>
<a-select-option v-for="(format, i) of precisionFormats" :key="i" :value="format">
<div class="flex flex-row items-center">
<div class="text-xs">
{{ (precisionFormatsDisplay as any)[format] }}
</div>
<div class="flex gap-2 w-full justify-between items-center">
{{ (precisionFormatsDisplay as any)[format] }}
<component
:is="iconMap.check"
v-if="vModel.meta.precision === format"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>

14
packages/nc-gui/components/smartsheet/column/DurationOptions.vue

@ -33,7 +33,19 @@ vModel.value.meta = {
<a-form-item :label="$t('labels.durationFormat')">
<a-select v-model:value="vModel.meta.duration" class="w-52" dropdown-class-name="nc-dropdown-duration-option">
<a-select-option v-for="(duration, i) of durationOptionList" :key="i" :value="duration.id">
{{ duration.title }}
<div class="flex gap-2 w-full truncate items-center">
<NcTooltip show-on-truncate-only class="flex-1 truncate">
<template #title> {{ duration.title }}</template>
{{ duration.title }}
</NcTooltip>
<component
:is="iconMap.check"
v-if="vModel.meta.duration === duration.id"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>

20
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -223,7 +223,7 @@ if (props.fromTableExplorer) {
:class="{
'bg-white': !props.fromTableExplorer,
'w-[400px]': !props.embedMode,
'!w-146': isTextArea(formState) && formState.meta.richMode,
'!w-146': isTextArea(formState) && formState.meta?.richMode,
'!w-[600px]': formState.uidt === UITypes.Formula && !props.embedMode,
'!w-[500px]': formState.uidt === UITypes.Attachment && !props.embedMode && !appInfo.ee,
'shadow-lg border-1 border-gray-200 shadow-gray-300 rounded-xl p-6': !embedMode,
@ -274,7 +274,7 @@ if (props.fromTableExplorer) {
show-search
class="nc-column-type-input !rounded"
:disabled="isKanban || readOnly"
dropdown-class-name="nc-dropdown-column-type border-1 border-gray-200"
dropdown-class-name="nc-dropdown-column-type border-1 !rounded-md border-gray-200"
@change="onUidtOrIdTypeChange"
@dblclick="showDeprecated = !showDeprecated"
>
@ -282,10 +282,16 @@ if (props.fromTableExplorer) {
<GeneralIcon icon="arrowDown" class="text-gray-700" />
</template>
<a-select-option v-for="opt of uiTypesOptions" :key="opt.name" :value="opt.name" v-bind="validateInfos.uidt">
<div class="flex gap-1 items-center">
<component :is="opt.icon" class="text-gray-700 mx-1" />
{{ opt.name }}
<div class="flex gap-2 items-center">
<component :is="opt.icon" class="text-gray-700" />
<div class="flex-1">{{ opt.name }}</div>
<span v-if="opt.deprecated" class="!text-xs !text-gray-300">({{ $t('general.deprecated') }})</span>
<component
:is="iconMap.check"
v-if="formState.uidt === opt.name"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
@ -338,7 +344,7 @@ if (props.fromTableExplorer) {
Default Value for JSON & LongText is not supported in MySQL
Default Value is Disabled for MSSQL -->
<LazySmartsheetColumnRichLongTextDefaultValue
v-if="isTextArea(formState) && formState.meta.richMode"
v-if="isTextArea(formState) && formState.meta?.richMode"
v-model:value="formState"
/>
<LazySmartsheetColumnDefaultValue
@ -353,7 +359,7 @@ if (props.fromTableExplorer) {
</div>
<div
v-if="!props.hideAdditionalOptions && !isVirtualCol(formState.uidt) && (!appInfo.ee || (appInfo.ee && !isXcdbBase(meta!.source_id) && formState.uidt === UITypes.SpecificDBType))"
v-if="!props.hideAdditionalOptions && !isVirtualCol(formState.uidt)&&!(!appInfo.ee && isAttachment(formState)) && (!appInfo.ee || (appInfo.ee && !isXcdbBase(meta!.source_id) && formState.uidt === UITypes.SpecificDBType))"
class="text-xs cursor-pointer text-gray-400 nc-more-options mb-1 mt-4 flex items-center gap-1 justify-end"
@click="advancedOptions = !advancedOptions"
>

41
packages/nc-gui/components/smartsheet/column/LookupOptions.vue

@ -83,15 +83,27 @@ const cellIcon = (column: ColumnType) =>
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.links')" v-bind="validateInfos.fk_relation_column_id">
<a-select
v-model:value="vModel.fk_relation_column_id"
dropdown-class-name="!w-64 nc-dropdown-relation-table"
dropdown-class-name="!w-64 !rounded-md nc-dropdown-relation-table"
@change="onRelationColChange"
>
<a-select-option v-for="(table, i) of refTables" :key="i" :value="table.col.fk_column_id">
<div class="flex flex-row h-full pb-0.5 items-center max-w-full">
<div class="font-semibold text-xs flex-shrink flex-grow-0 truncate">{{ table.column.title }}</div>
<div class="flex-grow"></div>
<div class="text-[0.65rem] text-gray-600 nc-relation-details">
<span class="uppercase">{{ table.col.type }}</span> {{ table.title || table.table_name }}
<div class="flex gap-2 w-full justify-between truncate items-center">
<NcTooltip class="font-semibold truncate min-w-1/2" show-on-truncate-only>
<template #title>{{ table.column.title }}</template>
{{ table.column.title }}</NcTooltip
>
<div class="inline-flex items-center truncate gap-2">
<div class="text-[0.65rem] flex-1 truncate text-gray-600 nc-relation-details">
<span class="uppercase">{{ table.col.type }}</span>
<span class="truncate">{{ table.title || table.table_name }}</span>
</div>
<component
:is="iconMap.check"
v-if="vModel.fk_relation_column_id === table.col.fk_column_id"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</div>
</a-select-option>
@ -102,13 +114,22 @@ const cellIcon = (column: ColumnType) =>
<a-select
v-model:value="vModel.fk_lookup_column_id"
name="fk_lookup_column_id"
dropdown-class-name="nc-dropdown-relation-column"
dropdown-class-name="nc-dropdown-relation-column !rounded-md"
@change="onDataTypeChange"
>
<a-select-option v-for="(column, index) of columns" :key="index" :value="column.id">
<div class="flex items-center -ml-1 font-semibold text-xs">
<component :is="cellIcon(column)" :column-meta="column" />
{{ column.title }}
<div class="flex gap-2 truncate items-center">
<div class="inline-flex items-center flex-1 truncate font-semibold">
<component :is="cellIcon(column)" :column-meta="column" />
<div class="truncate flex-1">{{ column.title }}</div>
</div>
<component
:is="iconMap.check"
v-if="vModel.fk_lookup_column_id === column.id"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>

19
packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue

@ -53,10 +53,25 @@ setAdditionalValidations({
>
<a-select
v-model:value="vModel.fk_qr_value_column_id"
:options="columnsAllowedAsQrValue"
:placeholder="$t('placeholder.selectAColumnForTheQRCodeValue')"
@click.stop
/>
>
<a-select-option v-for="opt of columnsAllowedAsQrValue" :key="opt" :value="opt.value">
<div class="flex gap-2 w-full truncate items-center" :data-testid="`nc-qr-${opt.label}`">
<NcTooltip show-on-truncate-only class="flex-1 truncate">
<template #title>{{ opt.label }}</template>
{{ opt.label }}
</NcTooltip>
<component
:is="iconMap.check"
v-if="vModel.fk_qr_value_column_id === opt.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>

43
packages/nc-gui/components/smartsheet/column/RatingOptions.vue

@ -74,20 +74,29 @@ watch(
<a-form-item :label="$t('labels.icon')">
<a-select v-model:value="vModel.meta.iconIdx" class="w-52" dropdown-class-name="nc-dropdown-rating-icon">
<a-select-option v-for="(icon, i) of iconList" :key="i" :value="i">
<div class="flex items-center">
<component
:is="getMdiIcon(icon.full)"
class="mx-1"
:style="{
color: vModel.meta.color,
}"
/>
<div class="flex gap-2 w-full truncate items-center">
<div class="flex-1">
<component
:is="getMdiIcon(icon.full)"
class="mx-1"
:style="{
color: vModel.meta.color,
}"
/>
<component
:is="getMdiIcon(icon.empty)"
:style="{
color: vModel.meta.color,
}"
/>
</div>
<component
:is="getMdiIcon(icon.empty)"
:style="{
color: vModel.meta.color,
}"
:is="iconMap.check"
v-if="vModel.meta.iconIdx === i"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
@ -98,7 +107,15 @@ watch(
<a-form-item :label="$t('labels.max')">
<a-select v-model:value="vModel.meta.max" class="w-52" dropdown-class-name="nc-dropdown-rating-color">
<a-select-option v-for="(v, i) in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" :key="i" :value="v">
{{ v }}
<div class="flex gap-2 w-full justify-between items-center">
{{ v }}
<component
:is="iconMap.check"
v-if="vModel.meta.max === v"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>

52
packages/nc-gui/components/smartsheet/column/RollupOptions.vue

@ -141,15 +141,27 @@ watch(
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.links')" v-bind="validateInfos.fk_relation_column_id">
<a-select
v-model:value="vModel.fk_relation_column_id"
dropdown-class-name="!w-64 nc-dropdown-relation-table"
dropdown-class-name="!w-64 nc-dropdown-relation-table !rounded-md"
@change="onRelationColChange"
>
<a-select-option v-for="(table, i) of refTables" :key="i" :value="table.col.fk_column_id">
<div class="flex flex-row h-full pb-0.5 items-center max-w-full">
<div class="font-semibold text-xs flex-shrink flex-grow-0 truncate">{{ table.column.title }}</div>
<div class="flex-grow"></div>
<div class="text-[0.65rem] text-gray-600 nc-relation-details">
<span class="uppercase">{{ table.col.type }}</span> {{ table.title || table.table_name }}
<div class="flex gap-2 w-full justify-between truncate items-center">
<NcTooltip class="font-semibold truncate min-w-1/2" show-on-truncate-only>
<template #title>{{ table.column.title }}</template>
{{ table.column.title }}</NcTooltip
>
<div class="inline-flex items-center truncate gap-2">
<div class="text-[0.65rem] flex-1 truncate text-gray-600 nc-relation-details">
<span class="uppercase">{{ table.col.type }}</span>
<span class="truncate">{{ table.title || table.table_name }}</span>
</div>
<component
:is="iconMap.check"
v-if="vModel.fk_relation_column_id === table.col.fk_column_id"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</div>
</a-select-option>
@ -160,14 +172,21 @@ watch(
<a-select
v-model:value="vModel.fk_rollup_column_id"
name="fk_rollup_column_id"
dropdown-class-name="nc-dropdown-relation-column"
dropdown-class-name="nc-dropdown-relation-column !rounded-xl"
@change="onDataTypeChange"
>
<a-select-option v-for="(column, index) of columns" :key="index" :value="column.id">
<div class="flex items-center -ml-1 font-semibold text-xs">
<component :is="cellIcon(column)" :column-meta="column" />
{{ column.title }}
<div class="flex gap-2 truncate items-center">
<div class="flex items-center flex-1 truncate font-semibold">
<component :is="cellIcon(column)" :column-meta="column" />
<div class="truncate flex-1">{{ column.title }}</div>
</div>
<component
:is="iconMap.check"
v-if="vModel.fk_rollup_column_id === column.id"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
@ -178,10 +197,19 @@ watch(
<a-select
v-model:value="vModel.rollup_function"
dropdown-class-name="nc-dropdown-rollup-function"
class="!mt-0.5"
@change="onDataTypeChange"
>
<a-select-option v-for="(func, index) of aggFunctionsList" :key="index" :value="func.value">
{{ func.text }}
<div class="flex gap-2 justify-between items-center">
{{ func.text }}
<component
:is="iconMap.check"
v-if="vModel.rollup_function === func.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>

10
packages/nc-gui/components/smartsheet/details/Api.vue

@ -169,7 +169,15 @@ watch(activeLang, (newLang) => {
dropdown-class-name="nc-dropdown-snippet-active-lang"
>
<a-select-option v-for="(client, i) in activeLang?.clients" :key="i" class="!w-full capitalize" :value="client">
{{ client }}
<div class="flex items-center w-full justify-between w-full gap-2">
<div class="truncate flex-1">{{ client }}</div>
<component
:is="iconMap.check"
v-if="selectedClient === client"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>

6
packages/nc-gui/components/smartsheet/details/Webhooks.vue

@ -256,10 +256,10 @@ watch(
<GeneralIcon icon="threeDotVertical" class="text-inherit" />
</NcButton>
<template #overlay>
<div class="flex flex-col p-0 items-start">
<div class="flex flex-col p-1.5 items-start">
<NcButton
type="text"
class="w-full !rounded-none"
class="w-full !rounded-md !px-2"
:loading="isCopying"
:centered="false"
@click="copyWebhook(hook)"
@ -267,7 +267,7 @@ watch(
<template #loading> {{ $t('general.duplicating') }} </template>
<div class="flex items-center gap-x-1"><GeneralIcon icon="copy" /> {{ $t('general.duplicate') }}</div>
</NcButton>
<NcButton type="text" class="w-full !rounded-none" :centered="false" @click="openDeleteModal(hook.id!)">
<NcButton type="text" class="w-full !rounded-md !px-2" :centered="false" @click="openDeleteModal(hook.id!)">
<div class="flex items-center justify-start gap-x-1 !text-red-500">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }}

26
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -1250,10 +1250,10 @@ onKeyStroke('ArrowDown', onDown)
<div
v-if="draggedCol"
:style="{
'min-width': gridViewCols[draggedCol.id!]?.width || '200px',
'max-width': gridViewCols[draggedCol.id!]?.width || '200px',
'width': gridViewCols[draggedCol.id!]?.width || '200px',
}"
'min-width': gridViewCols[draggedCol.id!]?.width || '200px',
'max-width': gridViewCols[draggedCol.id!]?.width || '200px',
'width': gridViewCols[draggedCol.id!]?.width || '200px',
}"
class="border-r-1 border-l-1 border-gray-200 h-full"
></div>
</div>
@ -1823,7 +1823,7 @@ onKeyStroke('ArrowDown', onDown)
<template #overlay>
<div class="relative overflow-visible min-h-17 w-10">
<div
class="absolute -top-19 flex flex-col h-34.5 w-70 bg-white rounded-lg border-1 border-gray-200 justify-start overflow-hidden"
class="absolute -top-19 flex flex-col min-h-34.5 w-70 p-1.5 bg-white rounded-lg border-1 border-gray-200 justify-start overflow-hidden"
style="box-shadow: 0px 4px 6px -2px rgba(0, 0, 0, 0.06), 0px -12px 16px -4px rgba(0, 0, 0, 0.1)"
:class="{
'-left-44': !isAddNewRecordGridMode,
@ -1832,7 +1832,7 @@ onKeyStroke('ArrowDown', onDown)
>
<div
v-e="['c:row:add:grid']"
class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer hover:bg-gray-100 text-gray-600 nc-new-record-with-grid group"
class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer rounded-md hover:bg-gray-100 text-gray-600 nc-new-record-with-grid group"
@click="onNewRecordToGridClick"
>
<div class="flex flex-row items-center justify-between w-full">
@ -1840,15 +1840,14 @@ onKeyStroke('ArrowDown', onDown)
<component :is="viewIcons[ViewTypes.GRID]?.icon" class="nc-view-icon text-inherit" />
{{ $t('activity.newRecord') }} - {{ $t('objects.viewType.grid') }}
</div>
<div class="h-4 w-4 flex flex-row items-center justify-center">
<GeneralIcon v-if="isAddNewRecordGridMode" icon="check" />
</div>
<GeneralIcon v-if="isAddNewRecordGridMode" icon="check" class="w-4 h-4 text-primary" />
</div>
<div class="flex flex-row text-xs text-gray-400 ml-7.25">{{ $t('labels.addRowGrid') }}</div>
</div>
<div
v-e="['c:row:add:form']"
class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer hover:bg-gray-100 text-gray-600 nc-new-record-with-form group"
class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer rounded-md hover:bg-gray-100 text-gray-600 nc-new-record-with-form group"
@click="onNewRecordToFormClick"
>
<div class="flex flex-row items-center justify-between w-full">
@ -1856,9 +1855,8 @@ onKeyStroke('ArrowDown', onDown)
<GeneralIcon class="h-4.5 w-4.5" icon="article" />
{{ $t('activity.newRecord') }} - {{ $t('objects.viewType.form') }}
</div>
<div class="h-4 w-4 flex flex-row items-center justify-center">
<GeneralIcon v-if="!isAddNewRecordGridMode" icon="check" />
</div>
<GeneralIcon v-if="!isAddNewRecordGridMode" icon="check" class="w-4 h-4 text-primary" />
</div>
<div class="flex flex-row text-xs text-gray-400 ml-7.05">{{ $t('labels.addRowForm') }}</div>
</div>
@ -1898,6 +1896,7 @@ onKeyStroke('ArrowDown', onDown)
.nc-grid-add-edit-column {
@apply bg-gray-50;
}
.nc-grid-add-new-cell:hover td {
@apply text-black !bg-gray-50;
}
@ -2018,6 +2017,7 @@ onKeyStroke('ArrowDown', onDown)
thead th:nth-child(2) {
@apply border-r-1 !border-r-gray-50;
}
tbody td:nth-child(2) {
@apply border-r-1 !border-r-gray-50;
}

74
packages/nc-gui/components/smartsheet/header/Menu.vue

@ -284,47 +284,47 @@ const onInsertAfter = () => {
v-model:visible="isOpen"
:trigger="['click']"
placement="bottomRight"
overlay-class-name="nc-dropdown-column-operations"
overlay-class-name="nc-dropdown-column-operations !border-1 rounded-lg !shadow-xl"
@click.stop="isOpen = !isOpen"
>
<div>
<GeneralIcon icon="arrowDown" class="text-grey h-full text-grey nc-ui-dt-dropdown cursor-pointer outline-0 mr-2" />
</div>
<template #overlay>
<a-menu class="shadow bg-white border-1 border-gray-200 nc-column-options">
<a-menu-item @click="onEditPress">
<NcMenu class="flex flex-col gap-1 border-gray-200 nc-column-options">
<NcMenuItem @click="onEditPress">
<div class="nc-column-edit nc-header-menu-item">
<component :is="iconMap.edit" class="text-gray-700 mx-0.65 my-0.75" />
<component :is="iconMap.edit" class="text-gray-700" />
<!-- Edit -->
{{ $t('general.edit') }}
</div>
</a-menu-item>
</NcMenuItem>
<a-divider v-if="!column?.pv" class="!my-0" />
<a-menu-item v-if="!column?.pv" @click="hideField">
<div v-e="['a:field:hide']" class="nc-column-insert-before nc-header-menu-item my-0.5">
<component :is="iconMap.eye" class="text-gray-700 mx-0.75 !w-3.75 !h-3.75 ml-0.75 mr-0.5" />
<NcMenuItem v-if="!column?.pv" @click="hideField">
<div v-e="['a:field:hide']" class="nc-column-insert-before nc-header-menu-item">
<component :is="iconMap.eye" class="text-gray-700 !w-3.75 !h-3.75" />
<!-- Hide Field -->
{{ $t('general.hideField') }}
</div>
</a-menu-item>
<a-menu-item v-if="(!virtual || column?.uidt === UITypes.Formula) && !column?.pv" @click="setAsDisplayValue">
<div class="nc-column-set-primary nc-header-menu-item item my-0.5">
<GeneralIcon icon="star" class="text-gray-700 !w-4.25 !h-4.25 ml-0.5 mr-0.25 -mt-0.5" />
</NcMenuItem>
<NcMenuItem v-if="(!virtual || column?.uidt === UITypes.Formula) && !column?.pv" @click="setAsDisplayValue">
<div class="nc-column-set-primary nc-header-menu-item item">
<GeneralIcon icon="star" class="text-gray-700 !w-4.25 !h-4.25" />
<!-- todo : tooltip -->
<!-- Set as Display value -->
{{ $t('activity.setDisplay') }}
</div>
</a-menu-item>
</NcMenuItem>
<a-divider class="!my-0" />
<a-divider v-if="!isLinksOrLTAR(column) || column.colOptions.type !== RelationTypes.BELONGS_TO" class="!my-0" />
<template v-if="!isLinksOrLTAR(column) || column.colOptions.type !== RelationTypes.BELONGS_TO">
<a-menu-item @click="sortByColumn('asc')">
<NcMenuItem @click="sortByColumn('asc')">
<div v-e="['a:field:sort', { dir: 'asc' }]" class="nc-column-insert-after nc-header-menu-item">
<component
:is="iconMap.sortDesc"
class="text-gray-700 !rotate-180 !w-4.25 !h-4.25 ml-0.5 mr-0.25"
class="text-gray-700 !rotate-180 !w-4.25 !h-4.25"
:style="{
transform: 'rotate(180deg)',
}"
@ -333,49 +333,49 @@ const onInsertAfter = () => {
<!-- Sort Ascending -->
{{ $t('general.sortAsc') }}
</div>
</a-menu-item>
<a-menu-item @click="sortByColumn('desc')">
</NcMenuItem>
<NcMenuItem @click="sortByColumn('desc')">
<div v-e="['a:field:sort', { dir: 'desc' }]" class="nc-column-insert-before nc-header-menu-item">
<component :is="iconMap.sortDesc" class="text-gray-700 !w-4.25 !h-4.25 ml-0.5 mr-0.25" />
<!-- Sort Descending -->
{{ $t('general.sortDesc') }}
</div>
</a-menu-item>
</NcMenuItem>
</template>
<a-divider class="!my-0" />
<a-divider v-if="!column?.pk" class="!my-0" />
<a-menu-item v-if="!column?.pk" @click="openDuplicateDlg">
<div v-e="['a:field:duplicate']" class="nc-column-duplicate nc-header-menu-item my-0.5">
<component :is="iconMap.duplicate" class="text-gray-700 mx-0.75" />
<NcMenuItem v-if="!column?.pk" @click="openDuplicateDlg">
<div v-e="['a:field:duplicate']" class="nc-column-duplicate nc-header-menu-item">
<component :is="iconMap.duplicate" class="text-gray-700" />
<!-- Duplicate -->
{{ t('general.duplicate') }}
</div>
</a-menu-item>
<a-menu-item @click="onInsertAfter">
</NcMenuItem>
<NcMenuItem @click="onInsertAfter">
<div v-e="['a:field:insert:after']" class="nc-column-insert-after nc-header-menu-item">
<component :is="iconMap.colInsertAfter" class="text-gray-700 !w-4.5 !h-4.5 ml-0.75" />
<component :is="iconMap.colInsertAfter" class="text-gray-700 !w-4.5 !h-4.5" />
<!-- Insert After -->
{{ t('general.insertAfter') }}
</div>
</a-menu-item>
<a-menu-item v-if="!column?.pv" @click="onInsertBefore">
</NcMenuItem>
<NcMenuItem v-if="!column?.pv" @click="onInsertBefore">
<div v-e="['a:field:insert:before']" class="nc-column-insert-before nc-header-menu-item">
<component :is="iconMap.colInsertBefore" class="text-gray-600 !w-4.5 !h-4.5 mr-1.5 -ml-0.75" />
<component :is="iconMap.colInsertBefore" class="text-gray-600 !w-4.5 !h-4.5" />
<!-- Insert Before -->
{{ t('general.insertBefore') }}
</div>
</a-menu-item>
<a-divider class="!my-0" />
</NcMenuItem>
<a-divider v-if="!column?.pv" class="!my-0" />
<a-menu-item v-if="!column?.pv" class="!hover:bg-red-50" @click="handleDelete">
<div class="nc-column-delete nc-header-menu-item my-0.75 text-red-600">
<component :is="iconMap.delete" class="ml-0.75 mr-1" />
<NcMenuItem v-if="!column?.pv" class="!hover:bg-red-50" @click="handleDelete">
<div class="nc-column-delete nc-header-menu-item text-red-600">
<component :is="iconMap.delete" />
<!-- Delete -->
{{ $t('general.delete') }}
</div>
</a-menu-item>
</a-menu>
</NcMenuItem>
</NcMenu>
</template>
</a-dropdown>
<SmartsheetHeaderDeleteColumnModal v-model:visible="showDeleteColumnModal" />
@ -390,7 +390,7 @@ const onInsertAfter = () => {
<style scoped>
.nc-header-menu-item {
@apply text-dropdown flex items-center px-1 py-2 gap-1;
@apply text-dropdown flex items-center gap-2;
}
.nc-column-options {

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

@ -361,9 +361,15 @@ onBeforeUnmount(() => {
@change="saveOrUpdate(filter, i)"
>
<a-select-option v-for="op in logicalOps" :key="op.value" :value="op.value">
<span class="capitalize">
{{ op.value }}
</span>
<div class="flex items-center w-full justify-between w-full gap-2">
<div class="truncate flex-1 capitalize">{{ op.value }}</div>
<component
:is="iconMap.check"
v-if="filter.logical_op === op.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>
</div>
@ -409,9 +415,15 @@ onBeforeUnmount(() => {
@click.stop
>
<a-select-option v-for="op of logicalOps" :key="op.value" :value="op.value">
<span class="capitalize">
{{ op.value }}
</span>
<div class="flex items-center w-full justify-between w-full gap-2">
<div class="truncate flex-1 capitalize">{{ op.value }}</div>
<component
:is="iconMap.check"
v-if="filter.logical_op === op.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>
<SmartsheetToolbarFieldListAutoCompleteDropdown
@ -433,7 +445,7 @@ onBeforeUnmount(() => {
variant="solo"
:disabled="filter.readOnly"
hide-details
dropdown-class-name="nc-dropdown-filter-comp-op"
dropdown-class-name="nc-dropdown-filter-comp-op !max-w-80"
@change="filterUpdateCondition(filter, i)"
>
<template
@ -441,7 +453,15 @@ onBeforeUnmount(() => {
:key="compOp.value"
>
<a-select-option v-if="isComparisonOpAllowed(filter, compOp)" :value="compOp.value">
{{ compOp.text }}
<div class="flex items-center w-full justify-between w-full gap-2">
<div class="truncate flex-1">{{ compOp.text }}</div>
<component
:is="iconMap.check"
v-if="filter.comparison_op === compOp.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</template>
</NcSelect>
@ -467,7 +487,18 @@ onBeforeUnmount(() => {
:key="compSubOp.value"
>
<a-select-option v-if="isComparisonSubOpAllowed(filter, compSubOp)" :value="compSubOp.value">
{{ compSubOp.text }}
<div class="flex items-center w-full justify-between w-full gap-2 max-w-40">
<NcTooltip show-on-truncate-only class="truncate flex-1">
<template #title>{{ compSubOp.text }}</template>
{{ compSubOp.text }}
</NcTooltip>
<component
:is="iconMap.check"
v-if="filter.comparison_sub_op === compSubOp.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</template>
</NcSelect>
@ -561,6 +592,7 @@ onBeforeUnmount(() => {
.nc-filter-item-remove-btn {
@apply text-gray-600 hover:text-gray-800;
}
.nc-filter-grid {
@apply items-center w-full;
}

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

@ -97,7 +97,7 @@ const onArrowUp = () => {
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 hover:bg-gray-100 cursor-pointer nc-sort-column-search-item"
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,
}"

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

@ -80,18 +80,26 @@ if (!localValue.value && allowEmpty !== true) {
dropdown-class-name="nc-dropdown-toolbar-field-list"
>
<a-select-option v-for="option in options" :key="option.value" :label="option.label" :value="option.value">
<div class="flex gap-2 items-center items-center h-full">
<component :is="option.icon" class="min-w-5 !mx-0" />
<NcTooltip
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
class="max-w-[15rem] truncate select-none"
show-on-truncate-only
>
<template #title> {{ option.label }}</template>
<template #default>
{{ option.label }}
</template>
</NcTooltip>
<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">
<component :is="option.icon" class="min-w-5 !mx-0" />
<NcTooltip
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
class="max-w-[15rem] truncate select-none"
show-on-truncate-only
>
<template #title> {{ option.label }}</template>
<span>
{{ option.label }}
</span>
</NcTooltip>
</div>
<component
:is="iconMap.check"
v-if="localValue === option.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>

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

@ -284,7 +284,15 @@ watch(meta, async () => {
:key="j"
:value="option.value"
>
<span>{{ option.text }}</span>
<div class="flex items-center justify-between gap-2">
<div class="truncate flex-1">{{ option.text }}</div>
<component
:is="iconMap.check"
v-if="group.sort === option.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>

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

@ -65,26 +65,20 @@ useMenuCloseOnEsc(open)
</div>
<template #overlay>
<div
class="w-full bg-white shadow-lg menu-filter-dropdown border-1 border-gray-200 rounded-md overflow-hidden"
class="w-full bg-white shadow-lg p-1.5 menu-filter-dropdown border-1 border-gray-200 rounded-md overflow-hidden"
data-testid="nc-height-menu"
>
<div class="flex flex-col w-full text-sm" @click.stop>
<div class="text-xs text-gray-500 px-3 pt-2 pb-1 select-none">{{ $t('objects.rowHeight') }}</div>
<div
class="nc-row-height-option"
:class="{'active': !(view?.view as GridType).row_height }"
@click="updateRowHeight(0)"
>
<div class="nc-row-height-option" @click="updateRowHeight(0)">
<GeneralIcon icon="heightShort" class="nc-row-height-icon" />
{{ $t('objects.heightClass.short') }}
<component :is="iconMap.check" v-if="!(view?.view as GridType).row_height" class="text-primary w-4 h-4" />
</div>
<div
class="nc-row-height-option"
:class="{'active': (view?.view as GridType).row_height === 1}"
@click="updateRowHeight(1)"
>
<div class="nc-row-height-option" @click="updateRowHeight(1)">
<GeneralIcon icon="heightMedium" class="nc-row-height-icon" />
{{ $t('objects.heightClass.medium') }}
<component :is="iconMap.check" v-if=" (view?.view as GridType).row_height === 1" class="text-primary w-4 h-4" />
</div>
<div
class="nc-row-height-option"
@ -93,6 +87,7 @@ useMenuCloseOnEsc(open)
>
<GeneralIcon icon="heightTall" class="nc-row-height-icon" />
{{ $t('objects.heightClass.tall') }}
<component :is="iconMap.check" v-if=" (view?.view as GridType).row_height === 2" class="text-primary w-4 h-4" />
</div>
<div
class="nc-row-height-option"
@ -101,6 +96,7 @@ useMenuCloseOnEsc(open)
>
<GeneralIcon icon="heightExtra" class="nc-row-height-icon" />
{{ $t('objects.heightClass.extra') }}
<component :is="iconMap.check" v-if=" (view?.view as GridType).row_height === 3" class="text-primary w-4 h-4" />
</div>
</div>
</div>
@ -110,14 +106,10 @@ useMenuCloseOnEsc(open)
<style scoped>
.nc-row-height-option {
@apply flex items-center py-2 pl-1 pr-2 justify-start hover:bg-gray-100 cursor-pointer text-gray-600;
@apply flex items-center gap-2 p-2 justify-start hover:bg-gray-100 rounded-md cursor-pointer text-gray-600;
}
.nc-row-height-icon {
@apply mx-2 text-base;
}
.active {
@apply bg-primary-selected;
@apply text-base;
}
</style>

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

@ -107,17 +107,23 @@ watch(columns, () => {
:open="isDropdownOpen"
size="small"
:dropdown-match-select-width="false"
dropdown-class-name="!rounded-lg nc-dropdown-toolbar-search-field-option w-48"
class="py-1 !absolute top-0 left-0 w-full h-full z-10 text-xs opacity-0"
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">
<div class="text-[0.75rem] flex items-center -ml-1 gap-2">
<SmartsheetHeaderIcon class="text-sm" :column="op.column" />
<NcTooltip class="truncate" placement="top" show-on-truncate-only>
<div class="text-sm flex items-center gap-2">
<SmartsheetHeaderIcon :column="op.column" />
<NcTooltip class="truncate flex-1" placement="top" show-on-truncate-only>
<template #title>{{ op.label }}</template>
<template #default>{{ op.label }}</template>
{{ op.label }}
</NcTooltip>
<component
:is="iconMap.check"
v-if="search.field === op.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
@ -138,14 +144,6 @@ watch(columns, () => {
</div>
</template>
<style lang="scss">
.nc-dropdown-toolbar-search-field-option {
.ant-select-item-option {
@apply !py-2 px-4;
}
}
</style>
<style scoped>
:deep(input::placeholder) {
@apply !text-gray-400;

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

@ -159,7 +159,15 @@ onMounted(() => {
v-e="['c:sort:operation:select']"
:value="option.value"
>
<span>{{ option.text }}</span>
<div class="flex items-center justify-between gap-2">
<div class="truncate flex-1">{{ option.text }}</div>
<component
:is="iconMap.check"
v-if="sort.direction === option.value"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>

30
packages/nc-gui/components/webhook/Editor.vue

@ -584,7 +584,15 @@ onMounted(async () => {
dropdown-class-name="nc-dropdown-webhook-event"
>
<a-select-option v-for="(event, i) in eventList" :key="i" class="capitalize" :value="event.value.join(' ')">
{{ event.text.join(' ') }}
<div class="flex items-center gap-2 justify-between">
<div>{{ event.text.join(' ') }}</div>
<component
:is="iconMap.check"
v-if="hookRef.eventOperation === event.value.join(' ')"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>
</a-form-item>
@ -601,7 +609,7 @@ onMounted(async () => {
@change="onNotificationTypeChange(true)"
>
<a-select-option v-for="(notificationOption, i) in notificationList" :key="i" :value="notificationOption.type">
<div class="flex items-center">
<div class="flex items-center gap-2">
<component :is="iconMap.link" v-if="notificationOption.type === 'URL'" class="mr-2" />
<component :is="iconMap.email" v-if="notificationOption.type === 'Email'" class="mr-2" />
@ -618,7 +626,13 @@ onMounted(async () => {
<MdiCellphoneMessage v-if="notificationOption.type === 'Twilio'" class="mr-2" />
{{ notificationOption.text }}
<div class="flex-1">{{ notificationOption.text }}</div>
<component
:is="iconMap.check"
v-if="hookRef.notification.type === notificationOption.type"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>
@ -636,7 +650,15 @@ onMounted(async () => {
dropdown-class-name="nc-dropdown-hook-notification-url-method"
>
<a-select-option v-for="(method, i) in methodList" :key="i" :value="method.title">
{{ method.title }}
<div class="flex items-center gap-2 justify-between">
<div>{{ method.title }}</div>
<component
:is="iconMap.check"
v-if="hookRef.notification.payload.method === method.title"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>
</a-col>

6
packages/nc-gui/utils/virtualCell.ts

@ -7,13 +7,13 @@ export const isLTAR = (uidt: string | undefined, colOptions: unknown): colOption
}
export const isHm = (column: ColumnType) =>
isLTAR(column.uidt!, column.colOptions) && column.colOptions.type === RelationTypes.HAS_MANY
isLTAR(column.uidt!, column.colOptions) && column.colOptions?.type === RelationTypes.HAS_MANY
export const isMm = (column: ColumnType) =>
isLTAR(column.uidt!, column.colOptions) && column.colOptions.type === RelationTypes.MANY_TO_MANY
isLTAR(column.uidt!, column.colOptions) && column.colOptions?.type === RelationTypes.MANY_TO_MANY
export const isBt = (column: ColumnType) =>
isLTAR(column.uidt!, column.colOptions) && column.colOptions.type === RelationTypes.BELONGS_TO
isLTAR(column.uidt!, column.colOptions) && column.colOptions?.type === RelationTypes.BELONGS_TO
export const isLookup = (column: ColumnType) => column.uidt === UITypes.Lookup
export const isRollup = (column: ColumnType) => column.uidt === UITypes.Rollup

9
tests/playwright/pages/Dashboard/Grid/Column/index.ts

@ -105,8 +105,8 @@ export class ColumnPageObject extends BasePage {
}
break;
case 'Date':
// Date Format
await this.get().locator('.nc-date-select').click();
await this.rootPage.locator('.nc-date-select').pressSequentially(dateFormat);
await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
break;
case 'DateTime':
@ -123,9 +123,8 @@ export class ColumnPageObject extends BasePage {
case 'QrCode':
await this.get().locator('.ant-select-single').nth(1).click();
await this.rootPage
.locator(`.ant-select-item`, {
hasText: new RegExp(`^${qrCodeValueColumnTitle}$`),
})
.locator(`.ant-select-item`)
.locator(`[data-testid="nc-qr-${qrCodeValueColumnTitle}"]`)
.click();
break;
case 'Barcode':
@ -316,8 +315,8 @@ export class ColumnPageObject extends BasePage {
await this.rootPage.locator('.ant-select-item').locator(`text="${timeFormat}"`).click();
break;
case 'Date':
// Date Format
await this.get().locator('.nc-date-select').click();
await this.rootPage.locator('.nc-date-select').pressSequentially(dateFormat);
await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
break;
default:

8
tests/playwright/pages/Dashboard/WebhookForm/index.ts

@ -154,13 +154,7 @@ export class WebhookFormPage extends BasePage {
await this.rootPage.waitForTimeout(500);
// kludge, as the dropdown is not visible even after scroll into view
await this.rootPage.locator('.ant-select-dropdown:visible').hover();
await this.rootPage
.locator('.ant-select-dropdown:visible')
.locator(`.ant-select-item`)
.last()
.scrollIntoViewIfNeeded();
await this.rootPage.locator('.nc-input-hook-header-key').pressSequentially(key);
await this.rootPage
.locator('.ant-select-dropdown:visible')
.locator(`.ant-select-item:has-text("${key}")`)

2
tests/playwright/tests/db/features/keyboardShortcuts.spec.ts

@ -173,7 +173,7 @@ test.describe('Clipboard support', () => {
{ column_name: 'MultiSelect', uidt: UITypes.MultiSelect, dtxp: "'Option1','Option2'" },
{ column_name: 'Rating', uidt: UITypes.Rating },
{ column_name: 'Checkbox', uidt: UITypes.Checkbox },
{ column_name: 'Date', uidt: UITypes.Date, meta: { date_format : 'YYYY-MM-DD' }},
{ column_name: 'Date', uidt: UITypes.Date, meta: { date_format: 'YYYY-MM-DD' } },
{ column_name: 'Attachment', uidt: UITypes.Attachment },
];

Loading…
Cancel
Save