Browse Source

feat(nc-gui): list option for user select

pull/7729/head
Ramesh Mane 10 months ago
parent
commit
3f5425cc82
  1. 341
      packages/nc-gui/components/cell/User.vue

341
packages/nc-gui/components/cell/User.vue

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onUnmounted } from '@vue/runtime-core' import { onUnmounted } from '@vue/runtime-core'
import tinycolor from 'tinycolor2' import tinycolor from 'tinycolor2'
import { CheckboxGroup, Checkbox, RadioGroup, Radio } from 'ant-design-vue'
import type { Select as AntSelect } from 'ant-design-vue' import type { Select as AntSelect } from 'ant-design-vue'
import type { UserFieldRecordType } from 'nocodb-sdk' import type { UserFieldRecordType } from 'nocodb-sdk'
import { import {
@ -146,6 +147,14 @@ const vModel = computed({
}, },
}) })
const vModelListLayout = computed(() => {
if (isMultiple) {
return (vModel.value || []).map((item) => item.value)
} else {
return (vModel.value || [])?.[0]?.value || ''
}
})
watch(isOpen, (n, _o) => { watch(isOpen, (n, _o) => {
if (!n) searchVal.value = '' if (!n) searchVal.value = ''
@ -266,87 +275,79 @@ const filterOption = (input: string, option: any) => {
:class="{ 'read-only': readOnly }" :class="{ 'read-only': readOnly }"
@click="toggleMenu" @click="toggleMenu"
> >
<div <div v-if="!isEditColumn && isForm && parseProp(column.meta)?.isList" class="w-full max-w-full">
v-if="!active" <component
class="flex flex-wrap" :is="isMultiple ? CheckboxGroup : RadioGroup"
:style="{ :model-value:value="vModelListLayout"
'display': '-webkit-box', class="nc-field-layout-list"
'max-width': '100%', @update:value="
'-webkit-line-clamp': rowHeight || 1, (value) => {
'-webkit-box-orient': 'vertical', // Todo: fix update single select user issue
'overflow': 'hidden', vModel = isMultiple ? value : [value]
}" }
> "
<template v-for="selectedOpt of vModel" :key="selectedOpt.value"> >
<a-tag class="rounded-tag max-w-full !pl-0" color="'#ccc'"> <template v-for="op of options" :key="op.id || op.email">
<span <component
:style="{ v-if="!op.deleted"
'color': tinycolor.isReadable('#ccc' || '#ccc', '#fff', { level: 'AA', size: 'large' }) :is="isMultiple ? Checkbox : Radio"
? '#fff' :key="op.id || op.email"
: tinycolor.mostReadable('#ccc' || '#ccc', ['#0b1d05', '#fff']).toHex8String(), :value="op.id"
'font-size': '13px', :data-testid="`select-option-${column.title}-${location === 'filter' ? 'filter' : rowIndex}`"
}" :class="`nc-select-option-${column.title}-${op.email}`"
class="flex items-stretch gap-2"
:class="{ 'text-sm': isKanban }"
> >
<div class="flex-none"> <a-tag class="rounded-tag max-w-full !pl-0" color="'#ccc'">
<GeneralUserIcon
size="auto"
:name="!selectedOpt.label?.includes('@') ? selectedOpt.label.trim() : ''"
:email="selectedOpt.label"
class="!text-[0.65rem]"
/>
</div>
<NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title>
{{ selectedOpt.label }}
</template>
<span <span
class="text-ellipsis overflow-hidden"
:style="{ :style="{
wordBreak: 'keep-all', 'color': tinycolor.isReadable('#ccc' || '#ccc', '#fff', { level: 'AA', size: 'large' })
whiteSpace: 'nowrap', ? '#fff'
display: 'inline', : tinycolor.mostReadable('#ccc' || '#ccc', ['#0b1d05', '#fff']).toHex8String(),
'font-size': '13px',
}" }"
class="flex items-stretch gap-2"
> >
{{ selectedOpt.label }} <div>
<GeneralUserIcon
size="auto"
:name="op.display_name?.trim() ? op.display_name?.trim() : ''"
:email="op.email"
class="!text-[0.65rem]"
/>
</div>
<NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title>
{{ op.display_name?.trim() || op.email }}
</template>
<span
class="text-ellipsis overflow-hidden"
:style="{
wordBreak: 'keep-all',
whiteSpace: 'nowrap',
display: 'inline',
}"
>
{{ op.display_name?.trim() || op.email }}
</span>
</NcTooltip>
</span> </span>
</NcTooltip> </a-tag>
</span> </component>
</a-tag> </template>
</template> </component>
</div> </div>
<template v-else>
<a-select <div
v-else v-if="!active"
ref="aselect" class="flex flex-wrap"
v-model:value="vModel" :style="{
mode="multiple" 'display': '-webkit-box',
class="w-full overflow-hidden" 'max-width': '100%',
:placeholder="isEditColumn ? $t('labels.optional') : ''" '-webkit-line-clamp': rowHeight || 1,
:bordered="false" '-webkit-box-orient': 'vertical',
clear-icon 'overflow': 'hidden',
:show-search="!isMobileMode" }"
:show-arrow="editAllowed && !readOnly" >
:open="isOpen && editAllowed" <template v-for="selectedOpt of vModel" :key="selectedOpt.value">
:disabled="readOnly || !editAllowed"
:class="{ 'caret-transparent': !hasEditRoles }"
:dropdown-class-name="`nc-dropdown-user-select-cell !min-w-200px ${isOpen ? 'active' : ''}`"
:filter-option="filterOption"
@search="search"
@keydown.stop
>
<template #suffixIcon>
<GeneralIcon icon="arrowDown" class="text-gray-700 nc-select-expand-btn" />
</template>
<template v-for="op of options" :key="op.id || op.email">
<a-select-option
v-if="!op.deleted"
:value="op.id"
:data-testid="`select-option-${column.title}-${location === 'filter' ? 'filter' : rowIndex}`"
:class="`nc-select-option-${column.title}-${op.email}`"
@click.stop
>
<a-tag class="rounded-tag max-w-full !pl-0" color="'#ccc'"> <a-tag class="rounded-tag max-w-full !pl-0" color="'#ccc'">
<span <span
:style="{ :style="{
@ -358,17 +359,17 @@ const filterOption = (input: string, option: any) => {
class="flex items-stretch gap-2" class="flex items-stretch gap-2"
:class="{ 'text-sm': isKanban }" :class="{ 'text-sm': isKanban }"
> >
<div> <div class="flex-none">
<GeneralUserIcon <GeneralUserIcon
size="auto" size="auto"
:name="op.display_name?.trim() ? op.display_name?.trim() : ''" :name="!selectedOpt.label?.includes('@') ? selectedOpt.label.trim() : ''"
:email="op.email" :email="selectedOpt.label"
class="!text-[0.65rem]" class="!text-[0.65rem]"
/> />
</div> </div>
<NcTooltip class="truncate max-w-full" show-on-truncate-only> <NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title> <template #title>
{{ op.display_name?.trim() || op.email }} {{ selectedOpt.label }}
</template> </template>
<span <span
class="text-ellipsis overflow-hidden" class="text-ellipsis overflow-hidden"
@ -378,51 +379,121 @@ const filterOption = (input: string, option: any) => {
display: 'inline', display: 'inline',
}" }"
> >
{{ op.display_name?.trim() || op.email }} {{ selectedOpt.label }}
</span> </span>
</NcTooltip> </NcTooltip>
</span> </span>
</a-tag> </a-tag>
</a-select-option> </template>
</template> </div>
<template #tagRender="{ label, value: val, onClose }"> <a-select
<a-tag v-else
v-if="options.find((el) => el.id === val)" ref="aselect"
class="rounded-tag nc-selected-option !pl-0" v-model:value="vModel"
:style="{ display: 'flex', alignItems: 'center' }" mode="multiple"
color="'#ccc'" class="w-full overflow-hidden"
:closable="editAllowed && ((vModel?.length ?? 0) > 1 || !column?.rqd)" :placeholder="isEditColumn ? $t('labels.optional') : ''"
:close-icon="h(MdiCloseCircle, { class: ['ms-close-icon'] })" :bordered="false"
@click="onTagClick($event, onClose)" clear-icon
@close="onClose" :show-search="!isMobileMode"
> :show-arrow="editAllowed && !readOnly"
<span :open="isOpen && editAllowed"
:style="{ :disabled="readOnly || !editAllowed"
'color': tinycolor.isReadable('#ccc' || '#ccc', '#fff', { :class="{ 'caret-transparent': !hasEditRoles }"
level: 'AA', :dropdown-class-name="`nc-dropdown-user-select-cell !min-w-200px ${isOpen ? 'active' : ''}`"
size: 'large', :filter-option="filterOption"
}) @search="search"
? '#fff' @keydown.stop
: tinycolor.mostReadable('#ccc' || '#ccc', ['#0b1d05', '#fff']).toHex8String(), >
'font-size': '13px', <template #suffixIcon>
}" <GeneralIcon icon="arrowDown" class="text-gray-700 nc-select-expand-btn" />
class="flex items-stretch gap-2" </template>
:class="{ 'text-sm': isKanban }" <template v-for="op of options" :key="op.id || op.email">
<a-select-option
v-if="!op.deleted"
:value="op.id"
:data-testid="`select-option-${column.title}-${location === 'filter' ? 'filter' : rowIndex}`"
:class="`nc-select-option-${column.title}-${op.email}`"
@click.stop
>
<a-tag class="rounded-tag max-w-full !pl-0" color="'#ccc'">
<span
:style="{
'color': tinycolor.isReadable('#ccc' || '#ccc', '#fff', { level: 'AA', size: 'large' })
? '#fff'
: tinycolor.mostReadable('#ccc' || '#ccc', ['#0b1d05', '#fff']).toHex8String(),
'font-size': '13px',
}"
class="flex items-stretch gap-2"
:class="{ 'text-sm': isKanban }"
>
<div>
<GeneralUserIcon
size="auto"
:name="op.display_name?.trim() ? op.display_name?.trim() : ''"
:email="op.email"
class="!text-[0.65rem]"
/>
</div>
<NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title>
{{ op.display_name?.trim() || op.email }}
</template>
<span
class="text-ellipsis overflow-hidden"
:style="{
wordBreak: 'keep-all',
whiteSpace: 'nowrap',
display: 'inline',
}"
>
{{ op.display_name?.trim() || op.email }}
</span>
</NcTooltip>
</span>
</a-tag>
</a-select-option>
</template>
<template #tagRender="{ label, value: val, onClose }">
<a-tag
v-if="options.find((el) => el.id === val)"
class="rounded-tag nc-selected-option !pl-0"
:style="{ display: 'flex', alignItems: 'center' }"
color="'#ccc'"
:closable="editAllowed && ((vModel?.length ?? 0) > 1 || !column?.rqd)"
:close-icon="h(MdiCloseCircle, { class: ['ms-close-icon'] })"
@click="onTagClick($event, onClose)"
@close="onClose"
> >
<div> <span
<GeneralUserIcon :style="{
size="auto" 'color': tinycolor.isReadable('#ccc' || '#ccc', '#fff', {
:name="!label?.includes('@') ? label.trim() : ''" level: 'AA',
:email="label" size: 'large',
class="!text-[0.65rem]" })
/> ? '#fff'
</div> : tinycolor.mostReadable('#ccc' || '#ccc', ['#0b1d05', '#fff']).toHex8String(),
{{ label }} 'font-size': '13px',
</span> }"
</a-tag> class="flex items-stretch gap-2"
</template> :class="{ 'text-sm': isKanban }"
</a-select> >
<div>
<GeneralUserIcon
size="auto"
:name="!label?.includes('@') ? label.trim() : ''"
:email="label"
class="!text-[0.65rem]"
/>
</div>
{{ label }}
</span>
</a-tag>
</template>
</a-select>
</template>
</div> </div>
</template> </template>
@ -492,4 +563,38 @@ const filterOption = (input: string, option: any) => {
:deep(.ant-select-selection-search-input) { :deep(.ant-select-selection-search-input) {
@apply !text-xs; @apply !text-xs;
} }
.nc-field-layout-list {
@apply !flex !flex-col !items-start w-full !space-y-0.5 !max-w-full;
:deep(.ant-checkbox-wrapper) {
@apply !m-0 !h-9 !mr-0 !flex !items-center w-full !max-w-full pl-2 rounded-lg hover:bg-gray-100;
&:hover {
.ant-checkbox-checked:after {
@apply !rounded;
}
}
.ant-checkbox {
@apply !top-0;
& + span {
@apply !flex !pl-4 max-w-[calc(100%_-_16px)];
}
.ant-checkbox-checked:after,
.ant-checkbox-inner {
@apply !rounded;
}
}
}
:deep(.ant-radio-wrapper) {
@apply !m-0 !h-9 !mr-0 !flex !items-center w-full !max-w-full pl-2 rounded-lg hover:bg-gray-100;
.ant-radio {
@apply !top-0;
}
.ant-radio + span {
@apply !flex !pl-4 max-w-[calc(100%_-_16px)];
}
}
}
</style> </style>

Loading…
Cancel
Save