Browse Source

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

Fix/dropdown UI
pull/7222/head
Raju Udava 12 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. 17
      packages/nc-gui/components/account/UserList.vue
  3. 23
      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. 12
      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. 56
      packages/nc-gui/components/roles/Selector.vue
  10. 14
      packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue
  11. 12
      packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue
  12. 20
      packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue
  13. 17
      packages/nc-gui/components/smartsheet/column/DateOptions.vue
  14. 16
      packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue
  15. 10
      packages/nc-gui/components/smartsheet/column/DecimalOptions.vue
  16. 12
      packages/nc-gui/components/smartsheet/column/DurationOptions.vue
  17. 20
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  18. 39
      packages/nc-gui/components/smartsheet/column/LookupOptions.vue
  19. 17
      packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue
  20. 19
      packages/nc-gui/components/smartsheet/column/RatingOptions.vue
  21. 48
      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. 18
      packages/nc-gui/components/smartsheet/grid/Table.vue
  25. 74
      packages/nc-gui/components/smartsheet/header/Menu.vue
  26. 48
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  27. 2
      packages/nc-gui/components/smartsheet/toolbar/CreateSort.vue
  28. 14
      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; overflow-y: visible !important;
} }
.rc-virtual-list-holder-inner {
@apply !px-1.5
}
.ant-layout-header { .ant-layout-header {
height: var(--topbar-height) !important; height: var(--topbar-height) !important;
} }
@ -445,7 +448,7 @@ a {
} }
.ant-dropdown-menu { .ant-dropdown-menu {
@apply !p-0 !rounded; @apply !p-0 !rounded-lg;
} }
.ant-tabs-dropdown-menu-title-content { .ant-tabs-dropdown-menu-title-content {
@ -505,8 +508,18 @@ a {
@apply p-4; @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) { .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 { .ant-select-selection-search-input:focus {
@ -591,7 +604,7 @@ a {
} }
.ant-popover-inner { .ant-popover-inner {
padding: 0 !important; @apply rounded-lg !p-0;
} }
.ant-popover-inner-content { .ant-popover-inner-content {
@apply !px-1.5 !py-1 text-xs; @apply !px-1.5 !py-1 text-xs;

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

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

23
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') }]"> <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> <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 <a-select-option
class="nc-role-option" class="nc-role-option"
:value="OrgUserRoles.CREATOR" :value="OrgUserRoles.CREATOR"
:label="$t(`objects.roleType.orgLevelCreator`)" :label="$t(`objects.roleType.orgLevelCreator`)"
> >
<div class="flex items-center gap-1 justify-between">
<div data-rec="true">{{ $t(`objects.roleType.orgLevelCreator`) }}</div> <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"> <span class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgCreator') }} {{ $t('msg.info.roles.orgCreator') }}
</span> </span>
@ -210,7 +223,15 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
:value="OrgUserRoles.VIEWER" :value="OrgUserRoles.VIEWER"
:label="$t(`objects.roleType.orgLevelViewer`)" :label="$t(`objects.roleType.orgLevelViewer`)"
> >
<div class="flex items-center gap-1 justify-between">
<div data-rec="true">{{ $t(`objects.roleType.orgLevelViewer`) }}</div> <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"> <span class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgViewer') }} {{ $t('msg.info.roles.orgViewer') }}
</span> </span>

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

@ -157,7 +157,7 @@ onMounted(() => {
</NcMenuItem> </NcMenuItem>
<template #content> <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 /> <LazyGeneralLanguageMenu />
</div> </div>
</template> </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" dropdown-class-name="nc-dropdown-ext-db-type"
@change="onClientChange" @change="onClientChange"
> >
<a-select-option v-for="client in clientTypes" :key="client.value" :value="client.value" <a-select-option v-for="client in clientTypes" :key="client.value" :value="client.value">
>{{ client.text }} <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-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -535,7 +543,17 @@ const toggleModal = (val: boolean) => {
dropdown-class-name="nc-dropdown-ssl-mode" dropdown-class-name="nc-dropdown-ssl-mode"
@select="onSSLModeChange" @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-select>
</a-form-item> </a-form-item>
@ -619,7 +637,17 @@ const toggleModal = (val: boolean) => {
v-model:value="formState.inflection.inflectionTable" v-model:value="formState.inflection.inflectionTable"
dropdown-class-name="nc-dropdown-inflection-table-name" 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-select>
</a-form-item> </a-form-item>
@ -628,7 +656,17 @@ const toggleModal = (val: boolean) => {
v-model:value="formState.inflection.inflectionColumn" v-model:value="formState.inflection.inflectionColumn"
dropdown-class-name="nc-dropdown-inflection-column-name" 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-select>
</a-form-item> </a-form-item>

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

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

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

@ -9,8 +9,8 @@
</div> </div>
<template #overlay> <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"> <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">
<GeneralLanguageMenu /> <div class="m-1.5"><GeneralLanguageMenu /></div>
</a-menu> </a-menu>
</template> </template>
</a-dropdown> </a-dropdown>

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

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

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

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

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

@ -73,9 +73,12 @@ vModel.value.au = !!vModel.value.au */
</div> </div>
<a-form-item :label="$t('labels.databaseType')" v-bind="validateInfos.dt"> <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"> <a-select-option v-for="type in dataTypes" :key="type" :value="type">
<div class="flex gap-2 items-center justify-between">
{{ type }} {{ 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-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -83,14 +86,19 @@ vModel.value.au = !!vModel.value.au */
<a-form-item v-if="!hideLength" :label="$t('labels.lengthValue')"> <a-form-item v-if="!hideLength" :label="$t('labels.lengthValue')">
<a-input <a-input
v-model:value="vModel.dtxp" v-model:value="vModel.dtxp"
class="!rounded-md" class="!rounded-md !mt-0.5"
:disabled="sqlUi.getDefaultLengthIsDisabled(vModel.dt) || !sqlUi.columnEditable(vModel)" :disabled="sqlUi.getDefaultLengthIsDisabled(vModel.dt) || !sqlUi.columnEditable(vModel)"
@input="onAlter" @input="onAlter"
/> />
</a-form-item> </a-form-item>
<a-form-item v-if="sqlUi.showScale(vModel)" label="Scale"> <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> </a-form-item>
<LazySmartsheetColumnPgBinaryOptions v-if="isPg(meta?.source_id) && vModel.dt === 'bytea'" v-model:value="vModel" /> <LazySmartsheetColumnPgBinaryOptions v-if="isPg(meta?.source_id) && vModel.dt === 'bytea'" v-model:value="vModel" />

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

@ -80,7 +80,8 @@ watch(
<a-form-item label="Icon"> <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 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"> <a-select-option v-for="(icon, i) of iconList" :key="i" :value="i">
<div class="flex items-center"> <div class="flex gap-2 w-full truncate items-center">
<div class="flex-1 truncate">
<component <component
:is="getMdiIcon(icon.checked)" :is="getMdiIcon(icon.checked)"
class="mx-1" class="mx-1"
@ -88,7 +89,6 @@ watch(
color: vModel.meta.color, color: vModel.meta.color,
}" }"
/> />
<component <component
:is="getMdiIcon(icon.unchecked)" :is="getMdiIcon(icon.unchecked)"
:style="{ :style="{
@ -96,6 +96,14 @@ watch(
}" }"
/> />
</div> </div>
<component
: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> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>

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

@ -89,7 +89,19 @@ currencyLocales().then((locales) => {
dropdown-class-name="nc-dropdown-currency-cell-locale" dropdown-class-name="nc-dropdown-currency-cell-locale"
> >
<a-select-option v-for="(currencyLocale, i) of currencyLocaleList" :key="i" :value="currencyLocale.value"> <a-select-option v-for="(currencyLocale, i) of currencyLocaleList" :key="i" :value="currencyLocale.value">
<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 }} {{ 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-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -106,7 +118,15 @@ currencyLocales().then((locales) => {
dropdown-class-name="nc-dropdown-currency-cell-code" dropdown-class-name="nc-dropdown-currency-cell-code"
> >
<a-select-option v-for="(currencyCode, i) of currencyList" :key="i" :value="currencyCode"> <a-select-option v-for="(currencyCode, i) of currencyList" :key="i" :value="currencyCode">
<div class="flex gap-2 w-full justify-between items-center">
{{ currencyCode }} {{ 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-option>
</a-select> </a-select>
</a-form-item> </a-form-item>

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

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

16
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-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" class="nc-date-select" dropdown-class-name="nc-dropdown-date-format">
<a-select-option v-for="(format, i) of dateFormats" :key="i" :value="format"> <a-select-option v-for="(format, i) of dateFormats" :key="i" :value="format">
<div class="flex gap-2 w-full justify-between items-center">
{{ format }} {{ 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-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item :label="$t('labels.timeFormat')"> <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 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"> <a-select-option v-for="(format, i) of timeFormats" :key="i" :value="format">
<div class="flex gap-2 w-full justify-between items-center" :data-testid="`nc-time-${format}`">
{{ 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-option>
</a-select> </a-select>
</a-form-item> </a-form-item>

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

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

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

@ -33,7 +33,19 @@ vModel.value.meta = {
<a-form-item :label="$t('labels.durationFormat')"> <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 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"> <a-select-option v-for="(duration, i) of durationOptionList" :key="i" :value="duration.id">
<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 }} {{ 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-option>
</a-select> </a-select>
</a-form-item> </a-form-item>

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

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

39
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-form-item class="flex w-1/2 pb-2" :label="$t('labels.links')" v-bind="validateInfos.fk_relation_column_id">
<a-select <a-select
v-model:value="vModel.fk_relation_column_id" 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" @change="onRelationColChange"
> >
<a-select-option v-for="(table, i) of refTables" :key="i" :value="table.col.fk_column_id"> <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="flex gap-2 w-full justify-between truncate items-center">
<div class="font-semibold text-xs flex-shrink flex-grow-0 truncate">{{ table.column.title }}</div> <NcTooltip class="font-semibold truncate min-w-1/2" show-on-truncate-only>
<div class="flex-grow"></div> <template #title>{{ table.column.title }}</template>
<div class="text-[0.65rem] text-gray-600 nc-relation-details"> {{ table.column.title }}</NcTooltip
<span class="uppercase">{{ table.col.type }}</span> {{ table.title || table.table_name }} >
<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>
</div> </div>
</a-select-option> </a-select-option>
@ -102,13 +114,22 @@ const cellIcon = (column: ColumnType) =>
<a-select <a-select
v-model:value="vModel.fk_lookup_column_id" v-model:value="vModel.fk_lookup_column_id"
name="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" @change="onDataTypeChange"
> >
<a-select-option v-for="(column, index) of columns" :key="index" :value="column.id"> <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"> <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" /> <component :is="cellIcon(column)" :column-meta="column" />
{{ column.title }} <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> </div>
</a-select-option> </a-select-option>
</a-select> </a-select>

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

@ -53,10 +53,25 @@ setAdditionalValidations({
> >
<a-select <a-select
v-model:value="vModel.fk_qr_value_column_id" v-model:value="vModel.fk_qr_value_column_id"
:options="columnsAllowedAsQrValue"
:placeholder="$t('placeholder.selectAColumnForTheQRCodeValue')" :placeholder="$t('placeholder.selectAColumnForTheQRCodeValue')"
@click.stop @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-form-item>
</a-col> </a-col>
</a-row> </a-row>

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

@ -74,7 +74,8 @@ watch(
<a-form-item :label="$t('labels.icon')"> <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 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"> <a-select-option v-for="(icon, i) of iconList" :key="i" :value="i">
<div class="flex items-center"> <div class="flex gap-2 w-full truncate items-center">
<div class="flex-1">
<component <component
:is="getMdiIcon(icon.full)" :is="getMdiIcon(icon.full)"
class="mx-1" class="mx-1"
@ -90,6 +91,14 @@ watch(
}" }"
/> />
</div> </div>
<component
: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> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -98,7 +107,15 @@ watch(
<a-form-item :label="$t('labels.max')"> <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 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"> <a-select-option v-for="(v, i) in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" :key="i" :value="v">
<div class="flex gap-2 w-full justify-between items-center">
{{ v }} {{ 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-option>
</a-select> </a-select>
</a-form-item> </a-form-item>

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

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

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

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

@ -1823,7 +1823,7 @@ onKeyStroke('ArrowDown', onDown)
<template #overlay> <template #overlay>
<div class="relative overflow-visible min-h-17 w-10"> <div class="relative overflow-visible min-h-17 w-10">
<div <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)" style="box-shadow: 0px 4px 6px -2px rgba(0, 0, 0, 0.06), 0px -12px 16px -4px rgba(0, 0, 0, 0.1)"
:class="{ :class="{
'-left-44': !isAddNewRecordGridMode, '-left-44': !isAddNewRecordGridMode,
@ -1832,7 +1832,7 @@ onKeyStroke('ArrowDown', onDown)
> >
<div <div
v-e="['c:row:add:grid']" 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" @click="onNewRecordToGridClick"
> >
<div class="flex flex-row items-center justify-between w-full"> <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" /> <component :is="viewIcons[ViewTypes.GRID]?.icon" class="nc-view-icon text-inherit" />
{{ $t('activity.newRecord') }} - {{ $t('objects.viewType.grid') }} {{ $t('activity.newRecord') }} - {{ $t('objects.viewType.grid') }}
</div> </div>
<div class="h-4 w-4 flex flex-row items-center justify-center">
<GeneralIcon v-if="isAddNewRecordGridMode" icon="check" /> <GeneralIcon v-if="isAddNewRecordGridMode" icon="check" class="w-4 h-4 text-primary" />
</div>
</div> </div>
<div class="flex flex-row text-xs text-gray-400 ml-7.25">{{ $t('labels.addRowGrid') }}</div> <div class="flex flex-row text-xs text-gray-400 ml-7.25">{{ $t('labels.addRowGrid') }}</div>
</div> </div>
<div <div
v-e="['c:row:add:form']" 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" @click="onNewRecordToFormClick"
> >
<div class="flex flex-row items-center justify-between w-full"> <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" /> <GeneralIcon class="h-4.5 w-4.5" icon="article" />
{{ $t('activity.newRecord') }} - {{ $t('objects.viewType.form') }} {{ $t('activity.newRecord') }} - {{ $t('objects.viewType.form') }}
</div> </div>
<div class="h-4 w-4 flex flex-row items-center justify-center">
<GeneralIcon v-if="!isAddNewRecordGridMode" icon="check" /> <GeneralIcon v-if="!isAddNewRecordGridMode" icon="check" class="w-4 h-4 text-primary" />
</div>
</div> </div>
<div class="flex flex-row text-xs text-gray-400 ml-7.05">{{ $t('labels.addRowForm') }}</div> <div class="flex flex-row text-xs text-gray-400 ml-7.05">{{ $t('labels.addRowForm') }}</div>
</div> </div>
@ -1898,6 +1896,7 @@ onKeyStroke('ArrowDown', onDown)
.nc-grid-add-edit-column { .nc-grid-add-edit-column {
@apply bg-gray-50; @apply bg-gray-50;
} }
.nc-grid-add-new-cell:hover td { .nc-grid-add-new-cell:hover td {
@apply text-black !bg-gray-50; @apply text-black !bg-gray-50;
} }
@ -2018,6 +2017,7 @@ onKeyStroke('ArrowDown', onDown)
thead th:nth-child(2) { thead th:nth-child(2) {
@apply border-r-1 !border-r-gray-50; @apply border-r-1 !border-r-gray-50;
} }
tbody td:nth-child(2) { tbody td:nth-child(2) {
@apply border-r-1 !border-r-gray-50; @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" v-model:visible="isOpen"
:trigger="['click']" :trigger="['click']"
placement="bottomRight" 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" @click.stop="isOpen = !isOpen"
> >
<div> <div>
<GeneralIcon icon="arrowDown" class="text-grey h-full text-grey nc-ui-dt-dropdown cursor-pointer outline-0 mr-2" /> <GeneralIcon icon="arrowDown" class="text-grey h-full text-grey nc-ui-dt-dropdown cursor-pointer outline-0 mr-2" />
</div> </div>
<template #overlay> <template #overlay>
<a-menu class="shadow bg-white border-1 border-gray-200 nc-column-options"> <NcMenu class="flex flex-col gap-1 border-gray-200 nc-column-options">
<a-menu-item @click="onEditPress"> <NcMenuItem @click="onEditPress">
<div class="nc-column-edit nc-header-menu-item"> <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 --> <!-- Edit -->
{{ $t('general.edit') }} {{ $t('general.edit') }}
</div> </div>
</a-menu-item> </NcMenuItem>
<a-divider v-if="!column?.pv" class="!my-0" /> <a-divider v-if="!column?.pv" class="!my-0" />
<a-menu-item v-if="!column?.pv" @click="hideField"> <NcMenuItem v-if="!column?.pv" @click="hideField">
<div v-e="['a:field:hide']" class="nc-column-insert-before nc-header-menu-item my-0.5"> <div v-e="['a:field:hide']" class="nc-column-insert-before nc-header-menu-item">
<component :is="iconMap.eye" class="text-gray-700 mx-0.75 !w-3.75 !h-3.75 ml-0.75 mr-0.5" /> <component :is="iconMap.eye" class="text-gray-700 !w-3.75 !h-3.75" />
<!-- Hide Field --> <!-- Hide Field -->
{{ $t('general.hideField') }} {{ $t('general.hideField') }}
</div> </div>
</a-menu-item> </NcMenuItem>
<a-menu-item v-if="(!virtual || column?.uidt === UITypes.Formula) && !column?.pv" @click="setAsDisplayValue"> <NcMenuItem 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"> <div class="nc-column-set-primary nc-header-menu-item item">
<GeneralIcon icon="star" class="text-gray-700 !w-4.25 !h-4.25 ml-0.5 mr-0.25 -mt-0.5" /> <GeneralIcon icon="star" class="text-gray-700 !w-4.25 !h-4.25" />
<!-- todo : tooltip --> <!-- todo : tooltip -->
<!-- Set as Display value --> <!-- Set as Display value -->
{{ $t('activity.setDisplay') }} {{ $t('activity.setDisplay') }}
</div> </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"> <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"> <div v-e="['a:field:sort', { dir: 'asc' }]" class="nc-column-insert-after nc-header-menu-item">
<component <component
:is="iconMap.sortDesc" :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="{ :style="{
transform: 'rotate(180deg)', transform: 'rotate(180deg)',
}" }"
@ -333,49 +333,49 @@ const onInsertAfter = () => {
<!-- Sort Ascending --> <!-- Sort Ascending -->
{{ $t('general.sortAsc') }} {{ $t('general.sortAsc') }}
</div> </div>
</a-menu-item> </NcMenuItem>
<a-menu-item @click="sortByColumn('desc')"> <NcMenuItem @click="sortByColumn('desc')">
<div v-e="['a:field:sort', { dir: 'desc' }]" class="nc-column-insert-before nc-header-menu-item"> <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" /> <component :is="iconMap.sortDesc" class="text-gray-700 !w-4.25 !h-4.25 ml-0.5 mr-0.25" />
<!-- Sort Descending --> <!-- Sort Descending -->
{{ $t('general.sortDesc') }} {{ $t('general.sortDesc') }}
</div> </div>
</a-menu-item> </NcMenuItem>
</template> </template>
<a-divider class="!my-0" /> <a-divider v-if="!column?.pk" class="!my-0" />
<a-menu-item v-if="!column?.pk" @click="openDuplicateDlg"> <NcMenuItem v-if="!column?.pk" @click="openDuplicateDlg">
<div v-e="['a:field:duplicate']" class="nc-column-duplicate nc-header-menu-item my-0.5"> <div v-e="['a:field:duplicate']" class="nc-column-duplicate nc-header-menu-item">
<component :is="iconMap.duplicate" class="text-gray-700 mx-0.75" /> <component :is="iconMap.duplicate" class="text-gray-700" />
<!-- Duplicate --> <!-- Duplicate -->
{{ t('general.duplicate') }} {{ t('general.duplicate') }}
</div> </div>
</a-menu-item> </NcMenuItem>
<a-menu-item @click="onInsertAfter"> <NcMenuItem @click="onInsertAfter">
<div v-e="['a:field:insert:after']" class="nc-column-insert-after nc-header-menu-item"> <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 --> <!-- Insert After -->
{{ t('general.insertAfter') }} {{ t('general.insertAfter') }}
</div> </div>
</a-menu-item> </NcMenuItem>
<a-menu-item v-if="!column?.pv" @click="onInsertBefore"> <NcMenuItem v-if="!column?.pv" @click="onInsertBefore">
<div v-e="['a:field:insert:before']" class="nc-column-insert-before nc-header-menu-item"> <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 --> <!-- Insert Before -->
{{ t('general.insertBefore') }} {{ t('general.insertBefore') }}
</div> </div>
</a-menu-item> </NcMenuItem>
<a-divider class="!my-0" /> <a-divider v-if="!column?.pv" class="!my-0" />
<a-menu-item v-if="!column?.pv" class="!hover:bg-red-50" @click="handleDelete"> <NcMenuItem 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"> <div class="nc-column-delete nc-header-menu-item text-red-600">
<component :is="iconMap.delete" class="ml-0.75 mr-1" /> <component :is="iconMap.delete" />
<!-- Delete --> <!-- Delete -->
{{ $t('general.delete') }} {{ $t('general.delete') }}
</div> </div>
</a-menu-item> </NcMenuItem>
</a-menu> </NcMenu>
</template> </template>
</a-dropdown> </a-dropdown>
<SmartsheetHeaderDeleteColumnModal v-model:visible="showDeleteColumnModal" /> <SmartsheetHeaderDeleteColumnModal v-model:visible="showDeleteColumnModal" />
@ -390,7 +390,7 @@ const onInsertAfter = () => {
<style scoped> <style scoped>
.nc-header-menu-item { .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 { .nc-column-options {

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

@ -361,9 +361,15 @@ onBeforeUnmount(() => {
@change="saveOrUpdate(filter, i)" @change="saveOrUpdate(filter, i)"
> >
<a-select-option v-for="op in logicalOps" :key="op.value" :value="op.value"> <a-select-option v-for="op in logicalOps" :key="op.value" :value="op.value">
<span class="capitalize"> <div class="flex items-center w-full justify-between w-full gap-2">
{{ op.value }} <div class="truncate flex-1 capitalize">{{ op.value }}</div>
</span> <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> </a-select-option>
</NcSelect> </NcSelect>
</div> </div>
@ -409,9 +415,15 @@ onBeforeUnmount(() => {
@click.stop @click.stop
> >
<a-select-option v-for="op of logicalOps" :key="op.value" :value="op.value"> <a-select-option v-for="op of logicalOps" :key="op.value" :value="op.value">
<span class="capitalize"> <div class="flex items-center w-full justify-between w-full gap-2">
{{ op.value }} <div class="truncate flex-1 capitalize">{{ op.value }}</div>
</span> <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> </a-select-option>
</NcSelect> </NcSelect>
<SmartsheetToolbarFieldListAutoCompleteDropdown <SmartsheetToolbarFieldListAutoCompleteDropdown
@ -433,7 +445,7 @@ onBeforeUnmount(() => {
variant="solo" variant="solo"
:disabled="filter.readOnly" :disabled="filter.readOnly"
hide-details 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)" @change="filterUpdateCondition(filter, i)"
> >
<template <template
@ -441,7 +453,15 @@ onBeforeUnmount(() => {
:key="compOp.value" :key="compOp.value"
> >
<a-select-option v-if="isComparisonOpAllowed(filter, compOp)" :value="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> </a-select-option>
</template> </template>
</NcSelect> </NcSelect>
@ -467,7 +487,18 @@ onBeforeUnmount(() => {
:key="compSubOp.value" :key="compSubOp.value"
> >
<a-select-option v-if="isComparisonSubOpAllowed(filter, compSubOp)" :value="compSubOp.value"> <a-select-option v-if="isComparisonSubOpAllowed(filter, compSubOp)" :value="compSubOp.value">
<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 }} {{ 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> </a-select-option>
</template> </template>
</NcSelect> </NcSelect>
@ -561,6 +592,7 @@ onBeforeUnmount(() => {
.nc-filter-item-remove-btn { .nc-filter-item-remove-btn {
@apply text-gray-600 hover:text-gray-800; @apply text-gray-600 hover:text-gray-800;
} }
.nc-filter-grid { .nc-filter-grid {
@apply items-center w-full; @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" v-for="(option, index) in options"
:key="index" :key="index"
v-e="['c:sort:add:column:select']" 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="{ :class="{
'bg-gray-100': activeFieldIndex === index, 'bg-gray-100': activeFieldIndex === index,
}" }"

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

@ -80,7 +80,8 @@ if (!localValue.value && allowEmpty !== true) {
dropdown-class-name="nc-dropdown-toolbar-field-list" 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"> <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"> <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" /> <component :is="option.icon" class="min-w-5 !mx-0" />
<NcTooltip <NcTooltip
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }" :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
@ -88,11 +89,18 @@ if (!localValue.value && allowEmpty !== true) {
show-on-truncate-only show-on-truncate-only
> >
<template #title> {{ option.label }}</template> <template #title> {{ option.label }}</template>
<template #default> <span>
{{ option.label }} {{ option.label }}
</template> </span>
</NcTooltip> </NcTooltip>
</div> </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> </a-select-option>
</NcSelect> </NcSelect>
</template> </template>

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

@ -284,7 +284,15 @@ watch(meta, async () => {
:key="j" :key="j"
:value="option.value" :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> </a-select-option>
</NcSelect> </NcSelect>

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

@ -65,26 +65,20 @@ useMenuCloseOnEsc(open)
</div> </div>
<template #overlay> <template #overlay>
<div <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" 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>
<div class="text-xs text-gray-500 px-3 pt-2 pb-1 select-none">{{ $t('objects.rowHeight') }}</div> <div class="text-xs text-gray-500 px-3 pt-2 pb-1 select-none">{{ $t('objects.rowHeight') }}</div>
<div <div class="nc-row-height-option" @click="updateRowHeight(0)">
class="nc-row-height-option"
:class="{'active': !(view?.view as GridType).row_height }"
@click="updateRowHeight(0)"
>
<GeneralIcon icon="heightShort" class="nc-row-height-icon" /> <GeneralIcon icon="heightShort" class="nc-row-height-icon" />
{{ $t('objects.heightClass.short') }} {{ $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>
<div <div class="nc-row-height-option" @click="updateRowHeight(1)">
class="nc-row-height-option"
:class="{'active': (view?.view as GridType).row_height === 1}"
@click="updateRowHeight(1)"
>
<GeneralIcon icon="heightMedium" class="nc-row-height-icon" /> <GeneralIcon icon="heightMedium" class="nc-row-height-icon" />
{{ $t('objects.heightClass.medium') }} {{ $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>
<div <div
class="nc-row-height-option" class="nc-row-height-option"
@ -93,6 +87,7 @@ useMenuCloseOnEsc(open)
> >
<GeneralIcon icon="heightTall" class="nc-row-height-icon" /> <GeneralIcon icon="heightTall" class="nc-row-height-icon" />
{{ $t('objects.heightClass.tall') }} {{ $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>
<div <div
class="nc-row-height-option" class="nc-row-height-option"
@ -101,6 +96,7 @@ useMenuCloseOnEsc(open)
> >
<GeneralIcon icon="heightExtra" class="nc-row-height-icon" /> <GeneralIcon icon="heightExtra" class="nc-row-height-icon" />
{{ $t('objects.heightClass.extra') }} {{ $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> </div>
</div> </div>
@ -110,14 +106,10 @@ useMenuCloseOnEsc(open)
<style scoped> <style scoped>
.nc-row-height-option { .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 { .nc-row-height-icon {
@apply mx-2 text-base; @apply text-base;
}
.active {
@apply bg-primary-selected;
} }
</style> </style>

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

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

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

@ -159,7 +159,15 @@ onMounted(() => {
v-e="['c:sort:operation:select']" v-e="['c:sort:operation:select']"
:value="option.value" :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> </a-select-option>
</NcSelect> </NcSelect>

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

@ -584,7 +584,15 @@ onMounted(async () => {
dropdown-class-name="nc-dropdown-webhook-event" dropdown-class-name="nc-dropdown-webhook-event"
> >
<a-select-option v-for="(event, i) in eventList" :key="i" class="capitalize" :value="event.value.join(' ')"> <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> </a-select-option>
</NcSelect> </NcSelect>
</a-form-item> </a-form-item>
@ -601,7 +609,7 @@ onMounted(async () => {
@change="onNotificationTypeChange(true)" @change="onNotificationTypeChange(true)"
> >
<a-select-option v-for="(notificationOption, i) in notificationList" :key="i" :value="notificationOption.type"> <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.link" v-if="notificationOption.type === 'URL'" class="mr-2" />
<component :is="iconMap.email" v-if="notificationOption.type === 'Email'" 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" /> <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> </div>
</a-select-option> </a-select-option>
</NcSelect> </NcSelect>
@ -636,7 +650,15 @@ onMounted(async () => {
dropdown-class-name="nc-dropdown-hook-notification-url-method" dropdown-class-name="nc-dropdown-hook-notification-url-method"
> >
<a-select-option v-for="(method, i) in methodList" :key="i" :value="method.title"> <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> </a-select-option>
</NcSelect> </NcSelect>
</a-col> </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) => 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) => 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) => 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 isLookup = (column: ColumnType) => column.uidt === UITypes.Lookup
export const isRollup = (column: ColumnType) => column.uidt === UITypes.Rollup 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; break;
case 'Date': case 'Date':
// Date Format
await this.get().locator('.nc-date-select').click(); 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(); await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
break; break;
case 'DateTime': case 'DateTime':
@ -123,9 +123,8 @@ export class ColumnPageObject extends BasePage {
case 'QrCode': case 'QrCode':
await this.get().locator('.ant-select-single').nth(1).click(); await this.get().locator('.ant-select-single').nth(1).click();
await this.rootPage await this.rootPage
.locator(`.ant-select-item`, { .locator(`.ant-select-item`)
hasText: new RegExp(`^${qrCodeValueColumnTitle}$`), .locator(`[data-testid="nc-qr-${qrCodeValueColumnTitle}"]`)
})
.click(); .click();
break; break;
case 'Barcode': case 'Barcode':
@ -316,8 +315,8 @@ export class ColumnPageObject extends BasePage {
await this.rootPage.locator('.ant-select-item').locator(`text="${timeFormat}"`).click(); await this.rootPage.locator('.ant-select-item').locator(`text="${timeFormat}"`).click();
break; break;
case 'Date': case 'Date':
// Date Format
await this.get().locator('.nc-date-select').click(); 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(); await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
break; break;
default: default:

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

@ -154,13 +154,7 @@ export class WebhookFormPage extends BasePage {
await this.rootPage.waitForTimeout(500); await this.rootPage.waitForTimeout(500);
// kludge, as the dropdown is not visible even after scroll into view // 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('.nc-input-hook-header-key').pressSequentially(key);
await this.rootPage
.locator('.ant-select-dropdown:visible')
.locator(`.ant-select-item`)
.last()
.scrollIntoViewIfNeeded();
await this.rootPage await this.rootPage
.locator('.ant-select-dropdown:visible') .locator('.ant-select-dropdown:visible')
.locator(`.ant-select-item:has-text("${key}")`) .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: 'MultiSelect', uidt: UITypes.MultiSelect, dtxp: "'Option1','Option2'" },
{ column_name: 'Rating', uidt: UITypes.Rating }, { column_name: 'Rating', uidt: UITypes.Rating },
{ column_name: 'Checkbox', uidt: UITypes.Checkbox }, { 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 }, { column_name: 'Attachment', uidt: UITypes.Attachment },
]; ];

Loading…
Cancel
Save