mirror of https://github.com/nocodb/nocodb
Browse Source
* fix(nc-gui): remove field modal title & type selector label * fix(nc-gui): hide default value input initially * fix(nc-gui): remove clear icon from default value input * fix(nc-gui): update email, phone, url column validation settings ui * fix(nc-gui): add missing validate field condition * fix(nc-gui): update long text field modal ui * fix(nc-gui): update default field modal width & enable rich text option * fix(nc-gui): small changes * fix(nc-gui): hide default value input only if user clicks on delete icon * fix(nc-gui): decimal field option ui * fix(nc-gui): update percent option ui * fix(nc-gui): small changes * fix(nc-gui): update field modal switch option alignment * fix(nc-gui): update date & dateTime field modal options * feat(nc-gui): add 12, 24 hrs time format option in field modal * feat(nc-gui): add 12hr time cell format support * fix(nc-gui): update time cell placeholder according to time format * fix(nc-gui): field modal default value option visibility issue * fix(nc-gui): update barcode, qr code field modal option * fix(nc-gui): field modal expanded json input modal overlay click issue * fix(nc-gui): currency code option from field modal * fix(nc-gui): udpate duration option * fix(nc-gui): date time cell clear icon visibility issue in link record dropdown * fix(nc-gui): update field modal lookup options * fix(nc-gui): update user option from field modal * fix(nc-gui): update rollup option from field modal * fix(nc-gui): update select field type ui for create column * fix(nc-gui): update field modal cancel & save btn alignment * fix(nc-gui): update formula option margin * fic(nc-gui): update select type option * fix(nc-gui): small changes * fix(nc-gui): update links field options * fix(nc-gui): small changes * fix(nc-gui): select option border issue * fix(nc-gui): add new color picker * fix(nc-gui): update rating field options * fix(nc-gui): update geodata field options * fix(nc-gui): geodata option small changes * fix(nc-gui): add new color picker for select type options * fix(nc-gui): show only title & field type list if uidt is null * feat(nc-gui): add 12hrs time support in dateTime cell * fix(nc-gui): formula suggestion list visibility issue * fix(nc-gui): reduce formaula suggestion fields icon size * fix(nc-gui): rich text default value visibility issue in field modal * fix(nc-gui): update rich text default value bubble menu option * fix(nc-gui): some pr review changes * fix(nc-gui): remove example from duration format * feat(nc-gui): add keyboard navigation support for field list * fix(nc-gui): update email, url, phone validate text * fix(nc-gui): update qr & barcode value select input * fix(nc-gui): pr review changes * test: update create column test cases * fix(nc-gui): remove required symbol from field modal inputs * fix(nc-gui): remove delete default value icon and add cross icon in default input itself * test: update duration field type test * fix(nc-gui): update column name & type input shadow * fix(nc-gui): add hover effect on selected type * fix(nc-gui): enabel rich text case update * fix(nc-gui): update select options * fix(nc-gui): show full time format in edit modal default value * fix(nc-gui): remove optional placeholder of default value * fix(nc-gui): instead of removing field type option disable it if it is onlyNameUpdateOnEditColumns * fix(nc-gui): update links field icons from field modal * fix(nc-gui): add links options in edit modal * fix(nc-gui): show links config and disable if it is not editable in edit column mode * fix(nc-gui): add support to configure date time format for create & last modified type field * fix(nc-gui): virtual field icon visibility issue if it is edit mode * fix(nc-gui): disabled edit option from field context menu if column is pk or system column * fix(nc-gui): update field modal submit btn text * fix(nc-gui): add shdow on input field in field modal * fix(nc-gui): disable submit btn if field modal has some warnings * test: update field add/edit save test case * test: update links column add/edit test cases * test: uncomment code * test: update user field default value update test cases * test: update select type option default value * test: update multi field editor test cases * test: update kanban view add option test cases * test: update multifield editor test cases * test: update create column keyboard shortcut test case * chore(nc-gui): lint * fix(nc-gui): field modal redio option shadow issue * fix(nc-gui): update field modal select option color picker btn border radius * fix(nc-gui): checkbox & rating icon alignment issue * fix(nc-gui): update field modal formula field * fix(nc-gui): field modal padding and gap issue * fix(nc-gui): update set default value font case & font color * fix(nc-gui): update field modal formula suggestion list ui * fix(nc-gui): removecolumn create field search list from multifield editor * fix(nc-gui): add placeholder for lookup & rollup options * fix: label * fix(nc-gui): remove placeholder from select type * fix(nc-gui): remove link type from link field select option * fix(nc-gui): qr, barcode value field icon issue * fix(nc-gui): set color picker tab according to active color * fix(nc-gui): json editor save btn ui changes in edit modal * fix(nc-gui): disable editing primary key col * chore(nc-gui): lint --------- Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>pull/8628/head
Ramesh Mane
5 months ago
committed by
GitHub
69 changed files with 2067 additions and 904 deletions
@ -0,0 +1,197 @@
|
||||
<script lang="ts" setup> |
||||
import tinycolor from 'tinycolor2' |
||||
import windiColors from 'windicss/colors' |
||||
import { themeV3Colors } from '../../utils/colorsUtils' |
||||
|
||||
interface Props { |
||||
modelValue?: string | any |
||||
isOpen?: boolean |
||||
} |
||||
|
||||
const props = withDefaults(defineProps<Props>(), { |
||||
isOpen: false, |
||||
}) |
||||
|
||||
const emit = defineEmits(['input', 'closeModal']) |
||||
|
||||
const { isOpen } = toRefs(props) |
||||
|
||||
const vModel = computed({ |
||||
get: () => props.modelValue, |
||||
set: (val) => { |
||||
emit('input', val || null) |
||||
}, |
||||
}) |
||||
|
||||
const showActiveColorTab = ref<boolean>(false) |
||||
|
||||
const picked = ref<string>(props.modelValue || enumColor.light[0]) |
||||
|
||||
const defaultColors = computed<string[][]>(() => { |
||||
const colors = [ |
||||
'gray', |
||||
'red', |
||||
'green', |
||||
'yellow', |
||||
'orange', |
||||
'pink', |
||||
'maroon', |
||||
'purple', |
||||
'blue', |
||||
] as (keyof typeof themeV3Colors)[] & (keyof typeof windiColors)[] |
||||
|
||||
const allColors = [] |
||||
|
||||
for (const color of colors) { |
||||
if (themeV3Colors[color]) { |
||||
allColors.push(color === 'gray' ? Object.values(themeV3Colors[color]).slice(1) : Object.values(themeV3Colors[color])) |
||||
} else if (windiColors[color]) { |
||||
allColors.push(Object.values(windiColors[color])) |
||||
} |
||||
} |
||||
return allColors |
||||
}) |
||||
|
||||
const localIsDefaultColorTab = ref(true) |
||||
|
||||
const isDefaultColorTab = computed({ |
||||
get: () => { |
||||
if (showActiveColorTab.value && vModel.value) { |
||||
for (const colorGrp of defaultColors.value) { |
||||
if (colorGrp.includes(vModel.value)) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
return localIsDefaultColorTab.value |
||||
}, |
||||
set: (val: boolean) => { |
||||
localIsDefaultColorTab.value = val |
||||
|
||||
if (showActiveColorTab.value) { |
||||
showActiveColorTab.value = false |
||||
} |
||||
}, |
||||
}) |
||||
|
||||
const selectColor = (color: string, closeModal = false) => { |
||||
picked.value = color |
||||
|
||||
if (closeModal) { |
||||
emit('closeModal') |
||||
} |
||||
} |
||||
|
||||
const compare = (colorA: string, colorB: string) => { |
||||
if (!colorA || !colorB) return false |
||||
|
||||
return colorA.toLowerCase() === colorB.toLowerCase() || colorA.toLowerCase() === tinycolor(colorB).toHex8String().toLowerCase() |
||||
} |
||||
|
||||
watch(picked, (n, _o) => { |
||||
vModel.value = n |
||||
}) |
||||
|
||||
watch( |
||||
isOpen, |
||||
(newValue) => { |
||||
if (newValue) { |
||||
showActiveColorTab.value = true |
||||
} |
||||
}, |
||||
{ |
||||
immediate: true, |
||||
}, |
||||
) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="nc-advance-color-picker w-[336px] pt-2" click.stop> |
||||
<NcTabs v-model:activeKey="isDefaultColorTab" class="nc-advance-color-picker-tab w-full"> |
||||
<a-tab-pane :key="true"> |
||||
<template #tab> |
||||
<div class="tab" data-testid="nc-default-colors-tab">Default colors</div> |
||||
</template> |
||||
<div class="h-full p-2"> |
||||
<div class="flex flex-col gap-1"> |
||||
<div v-for="(colorGroup, i) of defaultColors" :key="i" class="flex"> |
||||
<div v-for="(color, j) of colorGroup" :key="`color-${i}-${j}`" class="p-1 rounded-md flex h-8 hover:bg-gray-200"> |
||||
<button |
||||
class="color-selector" |
||||
:class="{ selected: compare(picked, color) }" |
||||
:style="{ |
||||
backgroundColor: `${color}`, |
||||
}" |
||||
@click="selectColor(color, true)" |
||||
></button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</a-tab-pane> |
||||
<a-tab-pane :key="false"> |
||||
<template #tab> |
||||
<div class="tab" data-testid="nc-custom-colors-tab"> |
||||
<div>Custom colours</div> |
||||
</div> |
||||
</template> |
||||
<div class="h-full p-2"> |
||||
<LazyGeneralChromeWrapper v-model="picked" class="!w-full !shadow-none" /> |
||||
</div> |
||||
</a-tab-pane> |
||||
</NcTabs> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.color-picker { |
||||
@apply flex flex-col items-center justify-center bg-white p-2.5; |
||||
} |
||||
.color-picker-row { |
||||
@apply flex flex-row space-x-1; |
||||
} |
||||
.color-selector { |
||||
@apply h-6 w-6 rounded; |
||||
-webkit-text-stroke-width: 1px; |
||||
-webkit-text-stroke-color: white; |
||||
} |
||||
.color-selector:hover { |
||||
filter: brightness(90%); |
||||
-webkit-filter: brightness(90%); |
||||
} |
||||
.color-selector:focus, |
||||
.color-selector.selected, |
||||
.nc-more-colors-trigger:focus { |
||||
outline: none; |
||||
box-shadow: 0px 0px 0px 2px #fff, 0px 0px 0px 4px #3069fe; |
||||
} |
||||
|
||||
:deep(.vc-chrome-toggle-icon) { |
||||
@apply !ml-3; |
||||
} |
||||
|
||||
:deep(.ant-tabs) { |
||||
@apply !overflow-visible; |
||||
.ant-tabs-nav { |
||||
@apply px-1; |
||||
.ant-tabs-nav-list { |
||||
@apply w-[99%] mx-auto gap-6; |
||||
|
||||
.ant-tabs-tab { |
||||
@apply flex-1 flex items-center justify-center pt-2 pb-2 text-xs font-semibold; |
||||
|
||||
& + .ant-tabs-tab { |
||||
@apply !ml-0; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.ant-tabs-content-holder { |
||||
.ant-tabs-content { |
||||
@apply h-full; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -1,28 +1,75 @@
|
||||
<script lang="ts" setup> |
||||
const props = defineProps<{ |
||||
value: any |
||||
isVisibleDefaultValueInput: boolean |
||||
}>() |
||||
const emits = defineEmits(['update:value']) |
||||
|
||||
provide(EditColumnInj, ref(true)) |
||||
|
||||
const vModel = useVModel(props, 'value', emits) |
||||
|
||||
const isVisibleDefaultValueInput = useVModel(props, 'isVisibleDefaultValueInput', emits) |
||||
|
||||
const cdfValue = computed({ |
||||
get: () => vModel.value.cdf, |
||||
set: (value) => { |
||||
vModel.value.cdf = value |
||||
if (value === '<br />') { |
||||
vModel.value.cdf = null |
||||
} else { |
||||
vModel.value.cdf = value |
||||
} |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div> |
||||
<div class="!my-3 text-xs">{{ $t('placeholder.defaultValue') }}</div> |
||||
<div v-if="!isVisibleDefaultValueInput"> |
||||
<NcButton |
||||
size="small" |
||||
type="text" |
||||
class="!text-gray-500 !hover:text-gray-700" |
||||
data-testid="nc-show-default-value-btn" |
||||
@click.stop="isVisibleDefaultValueInput = true" |
||||
> |
||||
<div class="flex items-center gap-2"> |
||||
<span>{{ $t('general.set') }} {{ $t('placeholder.defaultValue').toLowerCase() }}</span> |
||||
<GeneralIcon icon="plus" class="flex-none h-4 w-4" /> |
||||
</div> |
||||
</NcButton> |
||||
</div> |
||||
|
||||
<div v-else> |
||||
<div class="w-full flex items-center gap-2 mb-2"> |
||||
<div class="text-small leading-[18px] flex-1 text-gray-700">{{ $t('placeholder.defaultValue') }}</div> |
||||
</div> |
||||
<div class="flex flex-row gap-2"> |
||||
<div |
||||
class="border-1 relative pt-11 flex items-center w-full px-0 border-gray-300 rounded-md max-h-70 pb-1 focus-within:border-brand-500" |
||||
class="nc-default-value-wrapper nc-rich-long-text-default-value border-1 relative pt-7 flex items-center w-full px-0 border-gray-300 rounded-md max-h-70 pb-1 focus-within:(border-brand-500 shadow-selected) transition-all duration-0.3s" |
||||
> |
||||
<LazyCellRichText v-model:value="cdfValue" class="border-t-1 border-gray-100 !max-h-80 !min-h-30" show-menu /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.nc-rich-long-text-default-value { |
||||
:deep(.nc-rich-text) { |
||||
.bubble-menu.embed-mode.edit-column-mode { |
||||
@apply gap-x-0 p-0 h-7 border-0; |
||||
|
||||
.nc-button { |
||||
@apply !mt-0 h-7 p-1 min-w-7; |
||||
|
||||
svg { |
||||
@apply h-4 w-4; |
||||
} |
||||
} |
||||
.divider { |
||||
@apply !m-0 !h-7 border-gray-100; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
|
@ -1,3 +1,3 @@
|
||||
<template> |
||||
<div /> |
||||
<div class="hidden" /> |
||||
</template> |
||||
|
@ -0,0 +1,41 @@
|
||||
<script setup lang="ts"> |
||||
const props = defineProps<{ |
||||
value: any |
||||
}>() |
||||
|
||||
const emit = defineEmits(['update:value']) |
||||
|
||||
const vModel = useVModel(props, 'value', emit) |
||||
|
||||
// set default value |
||||
vModel.value.meta = { |
||||
is12hrFormat: false, |
||||
...(vModel.value.meta ?? {}), |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="flex flex-col gap-2"> |
||||
<div class="flex items-center gap-2 children:flex-1"> |
||||
<a-form-item> |
||||
<a-radio-group v-if="vModel.meta" v-model:value="vModel.meta.is12hrFormat" class="nc-time-form-layout"> |
||||
<a-radio :value="true">12 Hrs</a-radio> |
||||
<a-radio :value="false">24 Hrs</a-radio> |
||||
</a-radio-group> |
||||
</a-form-item> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
:deep(.nc-time-form-layout) { |
||||
@apply flex justify-between gap-2 children:(flex-1 m-0 px-2 py-1 border-1 border-gray-200 rounded-lg); |
||||
|
||||
.ant-radio-wrapper { |
||||
@apply transition-all; |
||||
&.ant-radio-wrapper-checked { |
||||
@apply border-brand-500; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,115 @@
|
||||
<script lang="ts" setup> |
||||
import type { UITypes } from 'nocodb-sdk' |
||||
import { UITypesName } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
options: typeof uiTypes |
||||
}>() |
||||
|
||||
const emits = defineEmits<{ selected: [UITypes] }>() |
||||
|
||||
const { options } = toRefs(props) |
||||
|
||||
const searchQuery = ref('') |
||||
|
||||
const filteredOptions = computed( |
||||
() => options.value?.filter((c) => c.name.toLowerCase().includes(searchQuery.value.toLowerCase())) ?? [], |
||||
) |
||||
|
||||
const inputRef = ref() |
||||
|
||||
const activeFieldIndex = ref(-1) |
||||
|
||||
const onClick = (uidt: UITypes) => { |
||||
if (!uidt) return |
||||
|
||||
emits('selected', uidt) |
||||
} |
||||
|
||||
const handleAutoScrollOption = () => { |
||||
const option = document.querySelector('.nc-field-list-option-active') |
||||
|
||||
if (option) { |
||||
setTimeout(() => { |
||||
option?.scrollIntoView({ behavior: 'smooth', block: 'center' }) |
||||
}, 50) |
||||
} |
||||
} |
||||
|
||||
const onArrowDown = () => { |
||||
activeFieldIndex.value = Math.min(activeFieldIndex.value + 1, filteredOptions.value.length - 1) |
||||
handleAutoScrollOption() |
||||
} |
||||
|
||||
const onArrowUp = () => { |
||||
activeFieldIndex.value = Math.max(activeFieldIndex.value - 1, 0) |
||||
handleAutoScrollOption() |
||||
} |
||||
|
||||
const handleKeydownEnter = () => { |
||||
if (filteredOptions.value[activeFieldIndex.value]) { |
||||
onClick(filteredOptions.value[activeFieldIndex.value].name) |
||||
} else if (filteredOptions.value[0]) { |
||||
onClick(filteredOptions.value[activeFieldIndex.value].name) |
||||
} |
||||
} |
||||
|
||||
onMounted(() => { |
||||
searchQuery.value = '' |
||||
activeFieldIndex.value = -1 |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
class="flex-1 border-1 border-gray-200 rounded-lg flex flex-col py-2" |
||||
data-testid="nc-column-uitypes-options-list-wrapper" |
||||
@keydown.arrow-down.prevent="onArrowDown" |
||||
@keydown.arrow-up.prevent="onArrowUp" |
||||
@keydown.enter.prevent="onClick(filteredOptions[activeFieldIndex].name)" |
||||
> |
||||
<div class="w-full pb-2 px-2" @click.stop> |
||||
<a-input |
||||
ref="inputRef" |
||||
v-model:value="searchQuery" |
||||
placeholder="Search field type" |
||||
class="nc-column-type-search-input nc-toolbar-dropdown-search-field-input" |
||||
@keydown.enter.stop="handleKeydownEnter" |
||||
@change="activeFieldIndex = 0" |
||||
> |
||||
<template #prefix> <GeneralIcon icon="search" class="nc-search-icon h-4 w-4 mr-1" /> </template> |
||||
</a-input> |
||||
</div> |
||||
<div class="nc-column-list-wrapper flex-col w-full max-h-[290px] nc-scrollbar-thin !overflow-y-auto px-2"> |
||||
<div v-if="!filteredOptions.length" class="px-2 py-6 text-gray-500 flex flex-col items-center gap-6"> |
||||
<img |
||||
src="~assets/img/placeholder/no-search-result-found.png" |
||||
class="!w-[164px] flex-none" |
||||
alt="No search results found" |
||||
/> |
||||
|
||||
{{ options.length ? $t('title.noResultsMatchedYourSearch') : 'The list is empty' }} |
||||
</div> |
||||
|
||||
<div |
||||
v-for="(option, index) in filteredOptions" |
||||
:key="index" |
||||
class="flex w-full py-2 items-center justify-between px-2 hover:bg-gray-100 cursor-pointer rounded-md" |
||||
:class="[ |
||||
`nc-column-list-option-${index}`, |
||||
{ |
||||
'bg-gray-100 nc-column-list-option-active': activeFieldIndex === index, |
||||
}, |
||||
]" |
||||
:data-testid="option.name" |
||||
@click="onClick(option.name)" |
||||
> |
||||
<div class="flex gap-2 items-center"> |
||||
<component :is="option.icon" class="text-gray-700 w-4 h-4" /> |
||||
<div class="flex-1 text-sm">{{ UITypesName[option.name] }}</div> |
||||
<span v-if="option.deprecated" class="!text-xs !text-gray-300">({{ $t('general.deprecated') }})</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
Loading…
Reference in new issue