Browse Source

feat(nc-gui): add single select list options layout

pull/7729/head
Ramesh Mane 7 months ago
parent
commit
5a4a6eb7d9
  1. 190
      packages/nc-gui/components/cell/SingleSelect.vue
  2. 60
      packages/nc-gui/components/smartsheet/Form.vue
  3. 5
      packages/nc-gui/lang/en.json

190
packages/nc-gui/components/cell/SingleSelect.vue

@ -272,82 +272,66 @@ const onFocus = () => {
<template>
<div
class="nc-cell-field h-full w-full flex items-center nc-single-select focus:outline-transparent"
:class="{ 'read-only': readOnly }"
:class="{ 'read-only': readOnly, 'max-w-full': isForm }"
@click="toggleMenu"
@keydown.enter.stop.prevent="toggleMenu"
>
<div v-if="!(active || isEditable)" class="w-full">
<a-tag v-if="selectedOpt" class="rounded-tag max-w-full" :color="selectedOpt.color">
<span
:style="{
'color': tinycolor.isReadable(selectedOpt.color || '#ccc', '#fff', { level: 'AA', size: 'large' })
? '#fff'
: tinycolor.mostReadable(selectedOpt.color || '#ccc', ['#0b1d05', '#fff']).toHex8String(),
'font-size': '13px',
}"
:class="{ 'text-sm': isKanban }"
<div v-if="!isEditColumn && isForm && parseProp(column.meta)?.isList" class="max-w-full">
<a-radio-group v-model:value="vModel" class="nc-field-layout-list">
<a-radio
v-for="op of options"
:key="op.title"
:value="op.title"
:data-testid="`select-option-${column.title}-${rowIndex}`"
:class="`nc-select-option-${column.title}-${op.title}`"
>
<NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title>
{{ selectedOpt.title }}
</template>
<a-tag class="rounded-tag max-w-full" :color="op.color">
<span
class="text-ellipsis overflow-hidden"
:style="{
wordBreak: 'keep-all',
whiteSpace: 'nowrap',
display: 'inline',
'color': tinycolor.isReadable(op.color || '#ccc', '#fff', { level: 'AA', size: 'large' })
? '#fff'
: tinycolor.mostReadable(op.color || '#ccc', ['#0b1d05', '#fff']).toHex8String(),
'font-size': '13px',
}"
>
{{ selectedOpt.title }}
<NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title>
{{ op.title }}
</template>
<span
class="text-ellipsis overflow-hidden"
:style="{
wordBreak: 'keep-all',
whiteSpace: 'nowrap',
display: 'inline',
}"
>
{{ op.title }}
</span>
</NcTooltip>
</span>
</NcTooltip>
</span>
</a-tag>
</a-tag></a-radio
>
</a-radio-group>
<div v-if="vModel" class="inline-block px-2 pt-2 cursor-pointer text-xs" @click="vModel = ''">
{{ $t('labels.clearSelection') }}
</div>
</div>
<NcSelect
v-else
ref="aselect"
v-model:value="vModel"
class="w-full overflow-hidden xs:min-h-12"
:class="{ 'caret-transparent': !hasEditRoles }"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
:allow-clear="!column.rqd && editAllowed"
:bordered="false"
:open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed"
:show-search="!isMobileMode && isOpen && active"
:show-arrow="hasEditRoles && !readOnly && active && (vModel === null || vModel === undefined)"
:dropdown-class-name="`nc-dropdown-single-select-cell !min-w-200px ${isOpen && active ? 'active' : ''}`"
:dropdown-match-select-width="true"
@select="onSelect"
@keydown="onKeydown($event)"
@search="search"
@blur="isOpen = false"
@focus="onFocus"
>
<a-select-option
v-for="op of options"
:key="op.title"
:value="op.title"
:data-testid="`select-option-${column.title}-${rowIndex}`"
:class="`nc-select-option-${column.title}-${op.title}`"
@click.stop
>
<a-tag class="rounded-tag max-w-full" :color="op.color">
<template v-else>
<div v-if="!(active || isEditable)" class="w-full">
<a-tag v-if="selectedOpt" class="rounded-tag max-w-full" :color="selectedOpt.color">
<span
:style="{
'color': tinycolor.isReadable(op.color || '#ccc', '#fff', { level: 'AA', size: 'large' })
'color': tinycolor.isReadable(selectedOpt.color || '#ccc', '#fff', { level: 'AA', size: 'large' })
? '#fff'
: tinycolor.mostReadable(op.color || '#ccc', ['#0b1d05', '#fff']).toHex8String(),
: tinycolor.mostReadable(selectedOpt.color || '#ccc', ['#0b1d05', '#fff']).toHex8String(),
'font-size': '13px',
}"
:class="{ 'text-sm': isKanban }"
>
<NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title>
{{ op.title }}
{{ selectedOpt.title }}
</template>
<span
class="text-ellipsis overflow-hidden"
@ -357,21 +341,80 @@ const onFocus = () => {
display: 'inline',
}"
>
{{ op.title }}
{{ selectedOpt.title }}
</span>
</NcTooltip>
</span>
</a-tag>
</a-select-option>
<a-select-option v-if="searchVal && isOptionMissing && isNewOptionCreateEnabled" :key="searchVal" :value="searchVal">
<div class="flex gap-2 text-gray-500 items-center h-full">
<component :is="iconMap.plusThick" class="min-w-4" />
<div class="text-xs whitespace-normal">
{{ $t('msg.selectOption.createNewOptionNamed') }} <strong>{{ searchVal }}</strong>
</div>
<NcSelect
v-else
ref="aselect"
v-model:value="vModel"
class="w-full overflow-hidden xs:min-h-12"
:class="{ 'caret-transparent': !hasEditRoles }"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
:allow-clear="!column.rqd && editAllowed"
:bordered="false"
:open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed"
:show-search="!isMobileMode && isOpen && active"
:show-arrow="hasEditRoles && !readOnly && active && (vModel === null || vModel === undefined)"
:dropdown-class-name="`nc-dropdown-single-select-cell !min-w-200px ${isOpen && active ? 'active' : ''}`"
:dropdown-match-select-width="true"
@select="onSelect"
@keydown="onKeydown($event)"
@search="search"
@blur="isOpen = false"
@focus="onFocus"
>
<a-select-option
v-for="op of options"
:key="op.title"
:value="op.title"
:data-testid="`select-option-${column.title}-${rowIndex}`"
:class="`nc-select-option-${column.title}-${op.title}`"
@click.stop
>
<a-tag class="rounded-tag max-w-full" :color="op.color">
<span
:style="{
'color': tinycolor.isReadable(op.color || '#ccc', '#fff', { level: 'AA', size: 'large' })
? '#fff'
: tinycolor.mostReadable(op.color || '#ccc', ['#0b1d05', '#fff']).toHex8String(),
'font-size': '13px',
}"
:class="{ 'text-sm': isKanban }"
>
<NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title>
{{ op.title }}
</template>
<span
class="text-ellipsis overflow-hidden"
:style="{
wordBreak: 'keep-all',
whiteSpace: 'nowrap',
display: 'inline',
}"
>
{{ op.title }}
</span>
</NcTooltip>
</span>
</a-tag>
</a-select-option>
<a-select-option v-if="searchVal && isOptionMissing && isNewOptionCreateEnabled" :key="searchVal" :value="searchVal">
<div class="flex gap-2 text-gray-500 items-center h-full">
<component :is="iconMap.plusThick" class="min-w-4" />
<div class="text-xs whitespace-normal">
{{ $t('msg.selectOption.createNewOptionNamed') }} <strong>{{ searchVal }}</strong>
</div>
</div>
</div>
</a-select-option>
</NcSelect>
</a-select-option>
</NcSelect>
</template>
</div>
</template>
@ -412,6 +455,19 @@ const onFocus = () => {
:deep(.ant-select-clear > span) {
@apply block;
}
.nc-field-layout-list {
@apply !flex !flex-col !items-start w-full !space-y-0.5 !max-w-full;
: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 lang="scss">

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

@ -157,6 +157,8 @@ const focusLabel: VNodeRef = (el) => {
const searchQuery = ref('')
const selectTypeFields = [UITypes.SingleSelect, UITypes.MultiSelect, UITypes.User]
const { t } = useI18n()
const { betaFeatureToggleState } = useBetaFeatureToggle()
@ -893,13 +895,13 @@ useEventListener(
:class="[
`nc-form-drag-${element.title.replaceAll(' ', '')}`,
{
'rounded-2xl overflow-hidden border-2 cursor-pointer my-1': isEditable,
'rounded-2xl overflow-hidden border-2 my-1': isEditable,
},
{
'p-4 lg:p-6 border-transparent my-0': !isEditable,
},
{
'nc-form-field-drag-handler border-transparent hover:(bg-gray-50) p-4 lg:p-6 ':
'nc-form-field-drag-handler border-transparent hover:(bg-gray-50) p-4 lg:p-6 cursor-pointer':
activeRow !== element.title && isEditable,
},
@ -1069,7 +1071,10 @@ useEventListener(
v-else
v-model="formState[element.title]"
class="nc-input truncate"
:class="`nc-form-input-${element.title.replaceAll(' ', '')}`"
:class="[
`nc-form-input-${element.title.replaceAll(' ', '')}`,
{ 'layout-list': element.meta.isList },
]"
:data-testid="`nc-form-input-${element.title.replaceAll(' ', '')}`"
:column="element"
:edit-enabled="true"
@ -1080,12 +1085,24 @@ useEventListener(
</div>
<!-- Field Settings -->
<!-- eslint-disable vue/no-constant-condition -->
<div
v-if="activeRow === element.title && false"
class="nc-form-field-settings border-t border-gray-200 p-4 lg:p-6"
v-if="activeRow === element.title"
class="nc-form-field-settings border-t border-gray-200 p-4 lg:p-6 flex flex-col gap-3"
>
<!-- Todo: Show on conditions, options limit,... -->
<!-- Layout -->
<div v-if="selectTypeFields.includes(element.uidt)">
<div>Layout</div>
<a-radio-group
v-model:value="element.meta.isList"
class="nc-form-field-layout !mt-2"
@change="updateColMeta(element)"
>
<a-radio :value="false">Dropdown</a-radio>
<a-radio :value="true">List</a-radio>
</a-radio-group>
</div>
<!-- Todo: Show on conditions,... -->
<div class="flex items-start gap-3 px-3 py-2 border-1 border-gray-200 rounded-lg">
<a-switch v-e="['a:form-view:field:show-on-condition']" size="small" />
<div>
@ -1093,6 +1110,17 @@ useEventListener(
<div class="text-gray-500">{{ $t('labels.showFieldOnConditionsMet') }}</div>
</div>
</div>
<!-- Limit options -->
<div
v-if="selectTypeFields.includes(element.uidt)"
class="flex items-start gap-3 px-3 py-2 border-1 border-gray-200 rounded-lg"
>
<a-switch v-e="['a:form-view:field:limit-options']" size="small" />
<div>
<div class="font-medium text-gray-800">{{ $t('labels.limitOptions') }}</div>
<div class="text-gray-500">{{ $t('labels.limitOptionsSubtext') }}.</div>
</div>
</div>
</div>
</div>
</template>
@ -1469,7 +1497,13 @@ useEventListener(
}
.nc-input {
@apply appearance-none w-full !bg-white rounded-lg border-solid border-1 border-gray-200 focus-within:border-brand-500;
@apply appearance-none w-full;
&:not(.layout-list) {
@apply !bg-white rounded-lg border-solid border-1 border-gray-200 focus-within:border-brand-500;
}
&.layout-list {
@apply h-auto !pl-0;
}
&.nc-cell-rating,
&.nc-cell-geodata {
@apply !py-1;
@ -1576,4 +1610,14 @@ useEventListener(
:deep(.nc-form-input-required + button):focus {
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #3366ff;
}
.nc-form-field-layout {
@apply !flex !items-center w-full space-x-3;
:deep(.ant-radio-wrapper) {
@apply border-1 border-gray-200 rounded-lg !py-2 !px-3 basis-full !mr-0 !items-center;
.ant-radio {
@apply !top-0;
}
}
}
</style>

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

@ -692,7 +692,10 @@
"backgroundColor":"Background Color",
"hideNocodbBranding":"Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showFieldOnConditionsMet":"Shows field only when conditions are met"
"showFieldOnConditionsMet":"Shows field only when conditions are met",
"limitOptions": "Limit ptions",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
"clearSelection": "Clear selection"
},
"activity": {
"noRange": "Calendar view requires a date range",

Loading…
Cancel
Save