chore: add hasMany text field feature to ui (#5213)

* chore: add hasMany text field feature to ui

* chore: remove bson dependency
This commit is contained in:
Dan Ribbens
2024-02-29 14:52:38 -05:00
committed by GitHub
parent b3bd502028
commit cccdba57fe
4 changed files with 117 additions and 40 deletions

View File

@@ -25,15 +25,15 @@
"get-port": "5.1.1",
"http-status": "1.6.2",
"mongoose": "6.12.3",
"mongoose-aggregate-paginate-v2": "1.0.6",
"mongoose-paginate-v2": "1.7.22",
"prompts": "2.4.2",
"uuid": "9.0.0"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"mongodb": "4.17.1",
"@types/mongoose-aggregate-paginate-v2": "1.0.9",
"bson": "^6.3.0",
"mongodb": "4.17.1",
"mongodb-memory-server": "^9",
"payload": "workspace:*"
},

View File

@@ -42,7 +42,7 @@ const NumberField: React.FC<Props> = (props) => {
const hasMany = 'hasMany' in props ? props.hasMany : false
const maxRows = 'maxRows' in props ? props.maxRows : Infinity
const { i18n } = useTranslation()
const { i18n, t } = useTranslation()
const memoizedValidate = useCallback(
(value, options) => {
@@ -73,7 +73,7 @@ const NumberField: React.FC<Props> = (props) => {
setValue(newVal)
},
[setValue],
[onChangeFromProps, setValue],
)
const [valueToRender, setValueToRender] = useState<
@@ -148,17 +148,17 @@ const NumberField: React.FC<Props> = (props) => {
isCreatable
isMulti
isSortable
// noOptionsMessage={({ inputValue }) => {
// const isOverHasMany = Array.isArray(value) && value.length >= maxRows
// if (isOverHasMany) {
// return t('validation:limitReached', { max: maxRows, value: value.length + 1 })
// }
// return null
// }}
noOptionsMessage={({ inputValue }) => {
const isOverHasMany = Array.isArray(value) && value.length >= maxRows
if (isOverHasMany) {
return t('validation:limitReached', { max: maxRows, value: value.length + 1 })
}
return null
}}
// numberOnly
// onChange={handleHasManyChange}
onChange={handleHasManyChange}
options={[]}
// placeholder={t('general:enterAValue')}
placeholder={t('general:enterAValue')}
showError={showError}
value={valueToRender as Option[]}
/>
@@ -174,7 +174,7 @@ const NumberField: React.FC<Props> = (props) => {
onChange={handleChange}
onWheel={(e) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// @ts-expect-error
e.target.blur()
}}
placeholder={getTranslation(placeholder, i18n)}

View File

@@ -2,10 +2,12 @@
import type { ClientValidate } from 'payload/types'
import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import type { Option } from '../../../elements/ReactSelect/types'
import type { Props } from './types'
import ReactSelect from '../../../elements/ReactSelect'
import { useConfig } from '../../../providers/Config'
import { useLocale } from '../../../providers/Locale'
import { useTranslation } from '../../../providers/Translation'
@@ -45,7 +47,7 @@ const Text: React.FC<Props> = (props) => {
const Label = LabelFromProps || <LabelComp label={label} required={required} />
const { i18n } = useTranslation()
const { i18n, t } = useTranslation()
const locale = useLocale()
@@ -71,9 +73,57 @@ const Text: React.FC<Props> = (props) => {
localizationConfig: localizationConfig || undefined,
})
const [valueToRender, setValueToRender] = useState<
{ id: string; label: string; value: { value: number } }[]
>([]) // Only for hasMany
const handleHasManyChange = useCallback(
(selectedOption) => {
if (!readOnly) {
let newValue
if (!selectedOption) {
newValue = []
} else if (Array.isArray(selectedOption)) {
newValue = selectedOption.map((option) => option.value?.value || option.value)
} else {
newValue = [selectedOption.value?.value || selectedOption.value]
}
setValue(newValue)
}
},
[readOnly, setValue],
)
// useEffect update valueToRender:
useEffect(() => {
if (hasMany && Array.isArray(value)) {
setValueToRender(
value.map((val, index) => {
return {
id: `${val}${index}`, // append index to avoid duplicate keys but allow duplicate numbers
label: `${val}`,
value: {
// React-select automatically uses "label-value" as a key, so we will get that react duplicate key warning if we just pass in the value as multiple values can be the same. So we need to append the index to the toString() of the value to avoid that warning, as it uses that as the key.
toString: () => `${val}${index}`,
value: val?.value || val,
},
}
}),
)
}
}, [value, hasMany])
return (
<div
className={[fieldBaseClass, 'text', className, showError && 'error', readOnly && 'read-only']
className={[
fieldBaseClass,
'number',
className,
showError && 'error',
readOnly && 'read-only',
hasMany && 'has-many',
]
.filter(Boolean)
.join(' ')}
style={{
@@ -83,24 +133,51 @@ const Text: React.FC<Props> = (props) => {
>
{Error}
{Label}
<div>
{BeforeInput}
<input
data-rtl={renderRTL}
{hasMany ? (
<ReactSelect
className={`field-${path.replace(/\./g, '__')}`}
disabled={readOnly}
id={`field-${path?.replace(/\./g, '__')}`}
name={path}
onChange={(e) => {
setValue(e.target.value)
// prevent adding additional options if maxRows is reached
filterOption={() =>
!maxRows ? true : !(Array.isArray(value) && maxRows && value.length >= maxRows)
}
isClearable
isCreatable
isMulti
isSortable
noOptionsMessage={() => {
const isOverHasMany = Array.isArray(value) && value.length >= maxRows
if (isOverHasMany) {
return t('validation:limitReached', { max: maxRows, value: value.length + 1 })
}
return null
}}
onKeyDown={onKeyDown}
placeholder={getTranslation(placeholder, i18n)}
ref={inputRef}
type="text"
value={(value as string) || ''}
onChange={handleHasManyChange}
options={[]}
placeholder={t('general:enterAValue')}
showError={showError}
value={valueToRender as Option[]}
/>
{AfterInput}
</div>
) : (
<div>
{BeforeInput}
<input
data-rtl={renderRTL}
disabled={readOnly}
id={`field-${path?.replace(/\./g, '__')}`}
name={path}
onChange={(e) => {
setValue(e.target.value)
}}
onKeyDown={onKeyDown}
placeholder={getTranslation(placeholder, i18n)}
ref={inputRef}
type="text"
value={(value as string) || ''}
/>
{AfterInput}
</div>
)}
{Description}
</div>
)

16
pnpm-lock.yaml generated
View File

@@ -330,6 +330,9 @@ importers:
mongoose:
specifier: 6.12.3
version: 6.12.3
mongoose-aggregate-paginate-v2:
specifier: 1.0.6
version: 1.0.6
mongoose-paginate-v2:
specifier: 1.7.22
version: 1.7.22
@@ -346,9 +349,6 @@ importers:
'@types/mongoose-aggregate-paginate-v2':
specifier: 1.0.9
version: 1.0.9
bson:
specifier: ^6.3.0
version: 6.3.0
mongodb:
specifier: 4.17.1
version: 4.17.1
@@ -6996,11 +6996,6 @@ packages:
engines: {node: '>=14.20.1'}
dev: true
/bson@6.3.0:
resolution: {integrity: sha512-balJfqwwTBddxfnidJZagCBPP/f48zj9Sdp3OJswREOgsJzHiQSaOIAtApSgDQFYgHqAvFkp53AFSqjMDZoTFw==}
engines: {node: '>=16.20.1'}
dev: true
/buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
dev: true
@@ -12314,6 +12309,11 @@ packages:
'@mongodb-js/saslprep': 1.1.4
dev: true
/mongoose-aggregate-paginate-v2@1.0.6:
resolution: {integrity: sha512-UuALu+mjhQa1K9lMQvjLL3vm3iALvNw8PQNIh2gp1b+tO5hUa0NC0Wf6/8QrT9PSJVTihXaD8hQVy3J4e0jO0Q==}
engines: {node: '>=4.0.0'}
dev: false
/mongoose-paginate-v2@1.7.22:
resolution: {integrity: sha512-xW5GugkE21DJiu9e13EOxKt4ejEKQkRP/S1PkkXRjnk2rRZVKBcld1nPV+VJ/YCPfm8hb3sz9OvI7O38RmixkA==}
engines: {node: '>=4.0.0'}