chore(next): wires server action into document edit view (#4873)
This commit is contained in:
@@ -6,6 +6,7 @@ const nextConfig = {
|
||||
},
|
||||
serverComponentsExternalPackages: ['drizzle-kit', 'drizzle-kit/utils', 'pino', 'pino-pretty'],
|
||||
},
|
||||
reactStrictMode: false,
|
||||
// transpilePackages: ['@payloadcms/db-mongodb', 'mongoose'],
|
||||
webpack: (config) => {
|
||||
return {
|
||||
|
||||
@@ -47,11 +47,13 @@ export const Pages: CollectionConfig = {
|
||||
name: 'number',
|
||||
label: 'Number',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'select',
|
||||
label: 'Select',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
@@ -67,6 +69,7 @@ export const Pages: CollectionConfig = {
|
||||
type: 'textarea',
|
||||
name: 'textarea',
|
||||
label: 'Textarea',
|
||||
required: true,
|
||||
admin: {
|
||||
rows: 10,
|
||||
},
|
||||
@@ -80,6 +83,7 @@ export const Pages: CollectionConfig = {
|
||||
name: 'groupText',
|
||||
label: 'Group Text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"@faceless-ui/modal": "2.0.1",
|
||||
"@payloadcms/translations": "workspace:^",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"deep-equal": "2.2.2",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"react-diff-viewer-continued": "3.2.6",
|
||||
"react-toastify": "8.2.0"
|
||||
|
||||
8
packages/next/src/pages/Login/LoginForm/index.scss
Normal file
8
packages/next/src/pages/Login/LoginForm/index.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.login__form {
|
||||
&__inputWrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base);
|
||||
margin-bottom: calc(var(--base) / 4);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,34 @@
|
||||
'use client'
|
||||
import {
|
||||
Email,
|
||||
Form,
|
||||
FormLoadingOverlayToggle,
|
||||
FormSubmit,
|
||||
Password,
|
||||
useConfig,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import { FormLoadingOverlayToggle } from '../../elements/Loading'
|
||||
import Form from '../../forms/Form'
|
||||
import FormSubmit from '../../forms/Submit'
|
||||
import { Email } from '../../forms/field-types/Email'
|
||||
import { Password } from '../../forms/field-types/Password'
|
||||
import Link from 'next/link'
|
||||
import { useTranslation } from '../../providers/Translation'
|
||||
import { useConfig } from '../../providers/Config'
|
||||
const baseClass = 'login__form'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'login-form'
|
||||
import Link from 'next/link'
|
||||
|
||||
export const LoginForm: React.FC<{
|
||||
action?: (formData: FormData) => Promise<void> | string
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}> = async ({ searchParams }) => {
|
||||
}> = ({ searchParams }) => {
|
||||
const config = useConfig()
|
||||
|
||||
const {
|
||||
admin: { autoLogin, user: userSlug },
|
||||
routes: { admin, api },
|
||||
} = config
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const prefillForm = autoLogin && autoLogin.prefillOnly
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Form
|
||||
action={`${api}/${userSlug}/login`}
|
||||
@@ -45,9 +46,9 @@ export const LoginForm: React.FC<{
|
||||
valid: true,
|
||||
},
|
||||
}}
|
||||
method="POST"
|
||||
redirect={`${admin}${searchParams?.redirect || ''}`}
|
||||
waitForAutocomplete
|
||||
method="POST"
|
||||
>
|
||||
<FormLoadingOverlayToggle action="loading" name="login-form" />
|
||||
<div className={`${baseClass}__inputWrap`}>
|
||||
@@ -6,6 +6,15 @@
|
||||
margin-bottom: calc(var(--base) * 2);
|
||||
}
|
||||
|
||||
&__form {
|
||||
&__inputWrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base);
|
||||
margin-bottom: calc(var(--base) / 4);
|
||||
}
|
||||
}
|
||||
|
||||
&__wrap {
|
||||
& > *:first-child {
|
||||
margin-top: 0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import { Logo } from '@payloadcms/ui/graphics'
|
||||
import { MinimalTemplate, LoginForm } from '@payloadcms/ui'
|
||||
import { MinimalTemplate } from '@payloadcms/ui'
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
import { meta } from '../../utilities/meta'
|
||||
import { Metadata } from 'next'
|
||||
@@ -9,6 +9,7 @@ import { initPage } from '../../utilities/initPage'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { getNextT } from '../../utilities/getNextT'
|
||||
import './index.scss'
|
||||
import { LoginForm } from './LoginForm'
|
||||
|
||||
const baseClass = 'login'
|
||||
|
||||
@@ -36,7 +37,7 @@ export const Login: React.FC<{
|
||||
const { config, user } = await initPage({ configPromise })
|
||||
|
||||
const {
|
||||
admin: { components: { afterLogin, beforeLogin } = {}, logoutRoute, user: userSlug },
|
||||
admin: { components: { afterLogin, beforeLogin } = {}, user: userSlug },
|
||||
routes: { admin },
|
||||
collections,
|
||||
} = config
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.json",
|
||||
"../ui/src/createClientConfig.ts"
|
||||
"../ui/src/createClientConfig.ts",
|
||||
"../dev/src/app/(payload)/admin/login/action.ts"
|
||||
],
|
||||
"references": [{ "path": "../payload" }, { "path": "../ui" }, { "path": "../translations" }]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export type ErrorProps = {
|
||||
alignCaret?: 'center' | 'left' | 'right'
|
||||
message: string
|
||||
message?: string
|
||||
path: string
|
||||
showError?: boolean
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
|
||||
export type LabelProps = {
|
||||
htmlFor?: string
|
||||
i18n: I18n
|
||||
label?: JSX.Element | Record<string, string> | false | string
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
3
packages/ui/src/config.ts
Normal file
3
packages/ui/src/config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { SanitizedConfig } from 'payload/config'
|
||||
|
||||
export default {} as Promise<SanitizedConfig>
|
||||
@@ -1,10 +0,0 @@
|
||||
@import '../../scss/styles';
|
||||
|
||||
.login-form {
|
||||
&__inputWrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: base(1);
|
||||
margin-bottom: base(0.25);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
export { Card } from '../elements/Card'
|
||||
export { Button } from '../elements/Button'
|
||||
export { RenderCustomComponent } from '../elements/RenderCustomComponent'
|
||||
export { LoginForm } from '../elements/LoginForm'
|
||||
export { TableColumnsProvider } from '../elements/TableColumns'
|
||||
export { HydrateClientUser } from '../elements/HydrateClientUser'
|
||||
export { DocumentHeader } from '../elements/DocumentHeader'
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
import { Tooltip } from '../../elements/Tooltip'
|
||||
import './index.scss'
|
||||
import type { ErrorProps } from 'payload/types'
|
||||
import { useFormFields } from '../Form/context'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'field-error'
|
||||
|
||||
const Error: React.FC<ErrorProps> = (props) => {
|
||||
const { alignCaret = 'right', message, showError = false } = props
|
||||
const {
|
||||
alignCaret = 'right',
|
||||
message: messageFromProps,
|
||||
path,
|
||||
showError: showErrorFromProps,
|
||||
} = props
|
||||
|
||||
if (showError) {
|
||||
const field = useFormFields(([fields]) => fields[path])
|
||||
|
||||
const { valid, errorMessage } = field || {}
|
||||
|
||||
const message = messageFromProps || errorMessage
|
||||
const showMessage = showErrorFromProps || !valid
|
||||
|
||||
if (showMessage) {
|
||||
return (
|
||||
<Tooltip alignCaret={alignCaret} className={baseClass} delay={0}>
|
||||
{message}
|
||||
|
||||
@@ -2,14 +2,14 @@ import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
// import { getTranslation } from '@payloadcms/translations'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { isComponent } from './types'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'field-description'
|
||||
|
||||
const FieldDescription: React.FC<Props> = (props) => {
|
||||
const { className, description, marginPlacement, path, value } = props
|
||||
const { className, description, marginPlacement, path, value, i18n } = props
|
||||
|
||||
if (isComponent(description)) {
|
||||
const Description = description
|
||||
@@ -27,14 +27,12 @@ const FieldDescription: React.FC<Props> = (props) => {
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{
|
||||
typeof description === 'function'
|
||||
? description({
|
||||
path,
|
||||
value,
|
||||
})
|
||||
: '' // : getTranslation(description, i18n)
|
||||
}
|
||||
{typeof description === 'function'
|
||||
? description({
|
||||
path,
|
||||
value,
|
||||
})
|
||||
: getTranslation(description, i18n)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
import { Description, DescriptionComponent } from 'payload/types'
|
||||
import React from 'react'
|
||||
|
||||
@@ -7,6 +8,7 @@ export type Props = {
|
||||
marginPlacement?: 'bottom' | 'top'
|
||||
path?: string
|
||||
value?: unknown
|
||||
i18n: I18n
|
||||
}
|
||||
|
||||
export function isComponent(description: Description): description is DescriptionComponent {
|
||||
|
||||
@@ -43,6 +43,7 @@ import getDataByPathFunc from './getDataByPath'
|
||||
import getSiblingDataFunc from './getSiblingData'
|
||||
import initContextState from './initContextState'
|
||||
import reduceFieldsToValues from './reduceFieldsToValues'
|
||||
import useDebounce from '../../hooks/useDebounce'
|
||||
|
||||
const baseClass = 'form'
|
||||
|
||||
@@ -59,14 +60,16 @@ const Form: React.FC<Props> = (props) => {
|
||||
// fields: fieldsFromProps = collection?.fields || global?.fields,
|
||||
handleResponse,
|
||||
initialState, // fully formed initial field state
|
||||
method,
|
||||
onSubmit,
|
||||
onSuccess,
|
||||
redirect,
|
||||
submitted: submittedFromProps,
|
||||
waitForAutocomplete,
|
||||
onChange,
|
||||
} = props
|
||||
|
||||
const method = 'method' in props ? props.method : undefined
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const { code: locale } = useLocale()
|
||||
@@ -89,6 +92,8 @@ const Form: React.FC<Props> = (props) => {
|
||||
*/
|
||||
const [fields, dispatchFields] = fieldsReducer
|
||||
|
||||
const debouncedFields = useDebounce(fields, 150)
|
||||
|
||||
contextRef.current.fields = fields
|
||||
contextRef.current.dispatchFields = dispatchFields
|
||||
|
||||
@@ -652,6 +657,30 @@ const Form: React.FC<Props> = (props) => {
|
||||
|
||||
const classes = [className, baseClass].filter(Boolean).join(' ')
|
||||
|
||||
useEffect(() => {
|
||||
const executeOnChange = async () => {
|
||||
if (Array.isArray(onChange)) {
|
||||
let newFormState
|
||||
|
||||
await onChange.reduce(async (priorOnChange, onChangeFn) => {
|
||||
await priorOnChange
|
||||
|
||||
const result = await onChangeFn({
|
||||
formState: debouncedFields,
|
||||
})
|
||||
|
||||
newFormState = result
|
||||
}, Promise.resolve())
|
||||
|
||||
if (!isDeepEqual(debouncedFields, newFormState)) {
|
||||
dispatchFields({ state: newFormState, type: 'REPLACE_STATE' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeOnChange()
|
||||
}, [debouncedFields])
|
||||
|
||||
return (
|
||||
<form
|
||||
action={action}
|
||||
|
||||
@@ -31,8 +31,15 @@ export type Preferences = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type Props = {
|
||||
action?: string | ((formData: FormData) => Promise<void>)
|
||||
export type Props = (
|
||||
| {
|
||||
action?: string
|
||||
method?: 'DELETE' | 'GET' | 'PATCH' | 'POST'
|
||||
}
|
||||
| {
|
||||
action: (formData: FormData) => Promise<void>
|
||||
}
|
||||
) & {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
disableSuccessStatus?: boolean
|
||||
@@ -46,13 +53,13 @@ export type Props = {
|
||||
handleResponse?: (res: Response) => void
|
||||
initialState?: FormState
|
||||
log?: boolean
|
||||
method?: 'DELETE' | 'GET' | 'PATCH' | 'POST'
|
||||
onSubmit?: (fields: FormState, data: Data) => void
|
||||
onSuccess?: (json: unknown) => void
|
||||
redirect?: string
|
||||
submitted?: boolean
|
||||
validationOperation?: 'create' | 'update'
|
||||
waitForAutocomplete?: boolean
|
||||
onChange?: ((args: { formState: FormState }) => Promise<FormState | void>)[]
|
||||
}
|
||||
|
||||
export type SubmitOptions = {
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import React from 'react'
|
||||
|
||||
// import { getTranslation } from '@payloadcms/translations'
|
||||
import './index.scss'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { LabelProps } from 'payload/types'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const Label: React.FC<LabelProps> = (props) => {
|
||||
const { htmlFor, label, required = false } = props
|
||||
// const { i18n } = useTranslation()
|
||||
const { htmlFor, label, required = false, i18n } = props
|
||||
|
||||
if (label) {
|
||||
return (
|
||||
<label className="field-label" htmlFor={htmlFor}>
|
||||
{typeof label === 'string' ? label : ''}
|
||||
{/* {getTranslation(label, i18n)} */}
|
||||
{getTranslation(label, i18n)}
|
||||
{required && <span className="required">*</span>}
|
||||
</label>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
import React, { Fragment, useCallback } from 'react'
|
||||
|
||||
import useField from '../../useField'
|
||||
import useField from '../../../useField'
|
||||
import { Validate } from 'payload/types'
|
||||
import { Check } from '../../../icons/Check'
|
||||
import { Line } from '../../../icons/Line'
|
||||
import { Check } from '../../../../icons/Check'
|
||||
import { Line } from '../../../../icons/Line'
|
||||
|
||||
type CheckboxInputProps = {
|
||||
'aria-label'?: string
|
||||
@@ -1,8 +1,6 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useFormFields } from '../../Form/context'
|
||||
|
||||
import './index.scss'
|
||||
import { useFormFields } from '../../../Form/context'
|
||||
|
||||
export const CheckboxWrapper: React.FC<{
|
||||
path: string
|
||||
@@ -12,7 +10,9 @@ export const CheckboxWrapper: React.FC<{
|
||||
}> = (props) => {
|
||||
const { path, children, readOnly, baseClass } = props
|
||||
|
||||
const { value: checked } = useFormFields(([fields]) => fields[path])
|
||||
const field = useFormFields(([fields]) => fields[path])
|
||||
|
||||
const { value: checked } = field || {}
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -13,7 +13,8 @@ import { CheckboxWrapper } from './Wrapper'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'checkbox'
|
||||
const inputBaseClass = 'checkbox-input'
|
||||
|
||||
export const inputBaseClass = 'checkbox-input'
|
||||
|
||||
const Checkbox: React.FC<Props> = (props) => {
|
||||
const {
|
||||
@@ -61,7 +62,7 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
<ErrorComp alignCaret="left" message={errorMessage} showError={!valid} />
|
||||
<ErrorComp alignCaret="left" path={path} />
|
||||
</div>
|
||||
<CheckboxWrapper path={path} readOnly={readOnly} baseClass={inputBaseClass}>
|
||||
<div className={`${inputBaseClass}__input`}>
|
||||
@@ -77,9 +78,9 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
/>
|
||||
{Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)}
|
||||
</div>
|
||||
{label && <LabelComp htmlFor={fieldID} label={label} required={required} />}
|
||||
{label && <LabelComp htmlFor={fieldID} label={label} required={required} i18n={i18n} />}
|
||||
</CheckboxWrapper>
|
||||
<FieldDescription description={description} path={path} value={value} />
|
||||
<FieldDescription description={description} path={path} value={value} i18n={i18n} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
import useField from '../../useField'
|
||||
import { useTranslation } from '../../../providers/Translation'
|
||||
import useField from '../../../useField'
|
||||
import { useTranslation } from '../../../../providers/Translation'
|
||||
import { Validate } from 'payload/types'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
export const EmailInput: React.FC<{
|
||||
name: string
|
||||
autoComplete?: string
|
||||
34
packages/ui/src/forms/field-types/Email/Wrapper/index.tsx
Normal file
34
packages/ui/src/forms/field-types/Email/Wrapper/index.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { fieldBaseClass } from '../../shared'
|
||||
import { useFormFields } from '../../../Form/context'
|
||||
|
||||
export const EmailInputWrapper: React.FC<{
|
||||
className?: string
|
||||
width?: string
|
||||
style?: React.CSSProperties
|
||||
readOnly?: boolean
|
||||
hasMany?: boolean
|
||||
children: React.ReactNode
|
||||
path: string
|
||||
}> = (props) => {
|
||||
const { className, readOnly, hasMany, style, width, children, path } = props
|
||||
|
||||
const field = useFormFields(([fields]) => fields[path])
|
||||
|
||||
const { valid } = field || {}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'email', className, !valid && 'error', readOnly && 'read-only']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
import { EmailInput } from './Input'
|
||||
import { EmailInputWrapper } from './Wrapper'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
export const Email: React.FC<Props> = (props) => {
|
||||
const {
|
||||
@@ -24,8 +24,8 @@ export const Email: React.FC<Props> = (props) => {
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
errorMessage,
|
||||
valid,
|
||||
i18n,
|
||||
value,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -34,24 +34,21 @@ export const Email: React.FC<Props> = (props) => {
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
fieldBaseClass,
|
||||
'email',
|
||||
className,
|
||||
// showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
<EmailInputWrapper
|
||||
className={className}
|
||||
readOnly={readOnly}
|
||||
style={style}
|
||||
width={width}
|
||||
path={path}
|
||||
>
|
||||
<ErrorComp message={errorMessage} showError={!valid} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<div className="input-wrapper">
|
||||
<ErrorComp path={path} />
|
||||
<LabelComp
|
||||
htmlFor={`field-${path.replace(/\./g, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
i18n={i18n}
|
||||
/>
|
||||
<div>
|
||||
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}
|
||||
<EmailInput
|
||||
name={name}
|
||||
@@ -62,12 +59,8 @@ export const Email: React.FC<Props> = (props) => {
|
||||
/>
|
||||
{Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)}
|
||||
</div>
|
||||
<FieldDescription
|
||||
description={description}
|
||||
path={path}
|
||||
// value={value}
|
||||
/>
|
||||
</div>
|
||||
<FieldDescription description={description} path={path} i18n={i18n} value={value} />
|
||||
</EmailInputWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { ErrorPill } from '../../../elements/ErrorPill'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import { createNestedFieldPath } from '../../Form/createNestedFieldPath'
|
||||
import RenderFields from '../../RenderFields'
|
||||
import { getNestedFieldState } from '../../WatchChildErrors/getNestedFieldState'
|
||||
import './index.scss'
|
||||
import { GroupProvider } from './provider'
|
||||
import { GroupWrapper } from './Wrapper'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'group-field'
|
||||
|
||||
const Group: React.FC<Props> = (props) => {
|
||||
@@ -28,6 +28,8 @@ const Group: React.FC<Props> = (props) => {
|
||||
user,
|
||||
i18n,
|
||||
payload,
|
||||
config,
|
||||
value,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -69,11 +71,12 @@ const Group: React.FC<Props> = (props) => {
|
||||
className={`field-description-${path.replace(/\./g, '__')}`}
|
||||
description={description}
|
||||
path={path}
|
||||
value={null}
|
||||
value={value}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</header>
|
||||
)}
|
||||
{groupHasErrors && <ErrorPill count={errorCount} withMessage />}
|
||||
{groupHasErrors && <ErrorPill count={errorCount} withMessage i18n={i18n} />}
|
||||
</div>
|
||||
<RenderFields
|
||||
fieldSchema={fieldSchema}
|
||||
@@ -87,6 +90,7 @@ const Group: React.FC<Props> = (props) => {
|
||||
formState={nestedFieldState}
|
||||
i18n={i18n}
|
||||
payload={payload}
|
||||
config={config}
|
||||
/>
|
||||
</div>
|
||||
</GroupProvider>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
'use client'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from '../../../providers/Translation'
|
||||
import { useTranslation } from '../../../../providers/Translation'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import useField from '../../useField'
|
||||
import './index.scss'
|
||||
import useField from '../../../useField'
|
||||
import { Validate } from 'payload/types'
|
||||
|
||||
export const NumberInput: React.FC<{
|
||||
41
packages/ui/src/forms/field-types/Number/Wrapper/index.tsx
Normal file
41
packages/ui/src/forms/field-types/Number/Wrapper/index.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { fieldBaseClass } from '../../shared'
|
||||
import { useFormFields } from '../../../Form/context'
|
||||
|
||||
export const NumberInputWrapper: React.FC<{
|
||||
className?: string
|
||||
width?: string
|
||||
style?: React.CSSProperties
|
||||
readOnly?: boolean
|
||||
hasMany?: boolean
|
||||
children: React.ReactNode
|
||||
path: string
|
||||
}> = (props) => {
|
||||
const { className, readOnly, hasMany, style, width, children, path } = props
|
||||
|
||||
const field = useFormFields(([fields]) => fields[path])
|
||||
|
||||
const { valid } = field || {}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
fieldBaseClass,
|
||||
'number',
|
||||
className,
|
||||
!valid && 'error',
|
||||
readOnly && 'read-only',
|
||||
hasMany && 'has-many',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { isNumber } from 'payload/utilities'
|
||||
import ReactSelect from '../../../elements/ReactSelect'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
import { NumberInput } from './Input'
|
||||
import { NumberInputWrapper } from './Wrapper'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const NumberField: React.FC<Props> = (props) => {
|
||||
const {
|
||||
@@ -26,14 +26,12 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
} = {},
|
||||
hasMany,
|
||||
label,
|
||||
max,
|
||||
maxRows,
|
||||
min,
|
||||
minRows,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
valid = true,
|
||||
errorMessage,
|
||||
i18n,
|
||||
value,
|
||||
} = props
|
||||
|
||||
@@ -43,24 +41,21 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
const path = pathFromProps || name
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
fieldBaseClass,
|
||||
'number',
|
||||
className,
|
||||
// showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
hasMany && 'has-many',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
<NumberInputWrapper
|
||||
className={className}
|
||||
readOnly={readOnly}
|
||||
hasMany={hasMany}
|
||||
style={style}
|
||||
width={width}
|
||||
path={path}
|
||||
>
|
||||
<ErrorComp message={errorMessage} showError={!valid} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp path={path} />
|
||||
<LabelComp
|
||||
htmlFor={`field-${path.replace(/\./g, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
i18n={i18n}
|
||||
/>
|
||||
{hasMany ? (
|
||||
<ReactSelect
|
||||
className={`field-${path.replace(/\./g, '__')}`}
|
||||
@@ -89,7 +84,7 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
// value={valueToRender as Option[]}
|
||||
/>
|
||||
) : (
|
||||
<div className="input-wrapper">
|
||||
<div>
|
||||
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}
|
||||
<NumberInput
|
||||
path={path}
|
||||
@@ -104,8 +99,8 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
{Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)}
|
||||
</div>
|
||||
)}
|
||||
<FieldDescription description={description} value={value} />
|
||||
</div>
|
||||
<FieldDescription description={description} value={value} i18n={i18n} />
|
||||
</NumberInputWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use client'
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
import useField from '../../useField'
|
||||
import './index.scss'
|
||||
import useField from '../../../useField'
|
||||
import { Validate } from 'payload/types'
|
||||
|
||||
export const PasswordInput: React.FC<{
|
||||
33
packages/ui/src/forms/field-types/Password/Wrapper/index.tsx
Normal file
33
packages/ui/src/forms/field-types/Password/Wrapper/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
import { fieldBaseClass } from '../../shared'
|
||||
import { useFormFields } from '../../../Form/context'
|
||||
|
||||
export const PasswordInputWrapper: React.FC<{
|
||||
className?: string
|
||||
width?: string
|
||||
style?: React.CSSProperties
|
||||
children: React.ReactNode
|
||||
path: string
|
||||
}> = (props) => {
|
||||
const { className, style, width, children, path } = props
|
||||
|
||||
const field = useFormFields(([fields]) => fields[path])
|
||||
|
||||
const { valid } = field || {}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'password', className, !valid && 'error']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import Error from '../../Error'
|
||||
import Label from '../../Label'
|
||||
import './index.scss'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import { PasswordInput } from './Input'
|
||||
import { PasswordInputWrapper } from './Wrapper'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
export const Password: React.FC<Props> = (props) => {
|
||||
const {
|
||||
@@ -19,33 +19,22 @@ export const Password: React.FC<Props> = (props) => {
|
||||
required,
|
||||
style,
|
||||
width,
|
||||
i18n,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
fieldBaseClass,
|
||||
'password',
|
||||
className,
|
||||
// showError && 'error'/
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
message=""
|
||||
// message={errorMessage}
|
||||
// showError={showError}
|
||||
<PasswordInputWrapper className={className} style={style} width={width} path={path}>
|
||||
<Error path={path} />
|
||||
<Label
|
||||
htmlFor={`field-${path.replace(/\./g, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
i18n={i18n}
|
||||
/>
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<PasswordInput name={name} autoComplete={autoComplete} disabled={disabled} path={path} />
|
||||
</div>
|
||||
</PasswordInputWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ import type React from 'react'
|
||||
|
||||
import type { Validate } from 'payload/types'
|
||||
import type { Description } from 'payload/types'
|
||||
import { FormFieldBase } from '../shared'
|
||||
|
||||
export type Props = {
|
||||
export type Props = FormFieldBase & {
|
||||
autoComplete?: string
|
||||
className?: string
|
||||
description?: Description
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
import { useTranslation } from '../../../providers/Translation'
|
||||
import { useTranslation } from '../../../../providers/Translation'
|
||||
import type { OptionObject, Validate } from 'payload/types'
|
||||
import type { Option } from '../../../elements/ReactSelect/types'
|
||||
import type { Option } from '../../../../elements/ReactSelect/types'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import ReactSelect from '../../../elements/ReactSelect'
|
||||
import useField from '../../useField'
|
||||
|
||||
import './index.scss'
|
||||
import ReactSelect from '../../../../elements/ReactSelect'
|
||||
import useField from '../../../useField'
|
||||
|
||||
const SelectInput: React.FC<{
|
||||
readOnly: boolean
|
||||
@@ -31,7 +29,7 @@ const SelectInput: React.FC<{
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { errorMessage, setValue, showError, value } = useField({
|
||||
const { setValue, showError, value } = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
35
packages/ui/src/forms/field-types/Select/Wrapper/index.tsx
Normal file
35
packages/ui/src/forms/field-types/Select/Wrapper/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
import { fieldBaseClass } from '../../shared'
|
||||
import { useFormFields } from '../../../Form/context'
|
||||
|
||||
export const SelectFieldWrapper: React.FC<{
|
||||
className?: string
|
||||
width?: string
|
||||
style?: React.CSSProperties
|
||||
readOnly?: boolean
|
||||
children: React.ReactNode
|
||||
path: string
|
||||
}> = (props) => {
|
||||
const { className, readOnly, style, width, children, path } = props
|
||||
|
||||
const field = useFormFields(([fields]) => fields[path])
|
||||
|
||||
const { valid } = field || {}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'select', className, !valid && 'error', readOnly && 'read-only']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import DefaultError from '../../Error'
|
||||
import DefaultLabel from '../../Label'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import SelectInput from './Input'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import { SelectFieldWrapper } from './Wrapper'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const formatOptions = (options: Option[]): OptionObject[] =>
|
||||
options.map((option) => {
|
||||
@@ -25,7 +27,6 @@ export const Select: React.FC<Props> = (props) => {
|
||||
name,
|
||||
admin: {
|
||||
className,
|
||||
// condition,
|
||||
description,
|
||||
isClearable,
|
||||
isSortable = true,
|
||||
@@ -39,6 +40,8 @@ export const Select: React.FC<Props> = (props) => {
|
||||
options,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
i18n,
|
||||
value,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -47,27 +50,20 @@ export const Select: React.FC<Props> = (props) => {
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
fieldBaseClass,
|
||||
'select',
|
||||
className,
|
||||
// showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
<SelectFieldWrapper
|
||||
className={className}
|
||||
style={style}
|
||||
width={width}
|
||||
path={path}
|
||||
readOnly={readOnly}
|
||||
>
|
||||
{/* <ErrorComp
|
||||
message={errorMessage}
|
||||
showError={showError}
|
||||
/> */}
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp path={path} />
|
||||
<LabelComp
|
||||
htmlFor={`field-${path.replace(/\./g, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
i18n={i18n}
|
||||
/>
|
||||
<SelectInput
|
||||
readOnly={readOnly}
|
||||
isClearable={isClearable}
|
||||
@@ -76,12 +72,8 @@ export const Select: React.FC<Props> = (props) => {
|
||||
options={formatOptions(options)}
|
||||
path={path}
|
||||
/>
|
||||
<FieldDescription
|
||||
description={description}
|
||||
path={path}
|
||||
// value={value}
|
||||
/>
|
||||
</div>
|
||||
<FieldDescription description={description} path={path} value={value} i18n={i18n} />
|
||||
</SelectFieldWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { SelectField } from 'payload/types'
|
||||
import { FormFieldBase } from '../shared'
|
||||
|
||||
export type Props = Omit<SelectField, 'type'> & {
|
||||
path?: string
|
||||
}
|
||||
export type Props = FormFieldBase &
|
||||
Omit<SelectField, 'type'> & {
|
||||
path?: string
|
||||
value?: string
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import React, { useCallback } from 'react'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import type { SanitizedConfig, Validate } from 'payload/types'
|
||||
|
||||
import { useTranslation } from '../../../providers/Translation'
|
||||
import { isFieldRTL } from '../shared'
|
||||
import './index.scss'
|
||||
import useField from '../../useField'
|
||||
import { useLocale } from '../../../providers/Locale'
|
||||
import { useTranslation } from '../../../../providers/Translation'
|
||||
import { isFieldRTL } from '../../shared'
|
||||
import useField from '../../../useField'
|
||||
import { useLocale } from '../../../../providers/Locale'
|
||||
|
||||
export const TextInput: React.FC<{
|
||||
name: string
|
||||
@@ -24,6 +23,7 @@ export const TextInput: React.FC<{
|
||||
minLength?: number
|
||||
validate?: Validate
|
||||
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void
|
||||
inputRef?: React.MutableRefObject<HTMLInputElement>
|
||||
}> = (props) => {
|
||||
const {
|
||||
path,
|
||||
@@ -37,6 +37,7 @@ export const TextInput: React.FC<{
|
||||
validate,
|
||||
required,
|
||||
onKeyDown,
|
||||
inputRef,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
@@ -50,13 +51,11 @@ export const TextInput: React.FC<{
|
||||
[validate, minLength, maxLength, required],
|
||||
)
|
||||
|
||||
const field = useField({
|
||||
const { setValue, value } = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
const { setValue, value } = field
|
||||
|
||||
const renderRTL = isFieldRTL({
|
||||
fieldLocalized: localized,
|
||||
fieldRTL: rtl,
|
||||
@@ -75,7 +74,7 @@ export const TextInput: React.FC<{
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
// ref={inputRef}
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={(value as string) || ''}
|
||||
/>
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.field-type.text {
|
||||
position: relative;
|
||||
36
packages/ui/src/forms/field-types/Text/Wrapper/index.tsx
Normal file
36
packages/ui/src/forms/field-types/Text/Wrapper/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
import { fieldBaseClass } from '../../shared'
|
||||
import { useFormFields } from '../../../Form/context'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
export const TextInputWrapper: React.FC<{
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
width?: string
|
||||
path?: string
|
||||
readOnly?: boolean
|
||||
}> = (props) => {
|
||||
const { children, className, style, width, path, readOnly } = props
|
||||
|
||||
const field = useFormFields(([fields]) => fields[path])
|
||||
|
||||
const { valid } = field || {}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'text', className, !valid && 'error', readOnly && 'read-only']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import { TextInput } from './Input'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import DefaultError from '../../Error'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { TextInputWrapper } from './Wrapper'
|
||||
|
||||
const Text: React.FC<Props> = (props) => {
|
||||
const {
|
||||
@@ -27,8 +26,8 @@ const Text: React.FC<Props> = (props) => {
|
||||
minLength,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
valid = true,
|
||||
errorMessage,
|
||||
value,
|
||||
i18n,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -37,18 +36,21 @@ const Text: React.FC<Props> = (props) => {
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'text', className, !valid && 'error', readOnly && 'read-only']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
<TextInputWrapper
|
||||
className={className}
|
||||
style={style}
|
||||
width={width}
|
||||
path={path}
|
||||
readOnly={readOnly}
|
||||
>
|
||||
<ErrorComp message={errorMessage} showError={!valid} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<div className="input-wrapper">
|
||||
<ErrorComp path={path} />
|
||||
<LabelComp
|
||||
htmlFor={`field-${path.replace(/\./g, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
i18n={i18n}
|
||||
/>
|
||||
<div>
|
||||
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}
|
||||
<TextInput
|
||||
path={path}
|
||||
@@ -66,9 +68,10 @@ const Text: React.FC<Props> = (props) => {
|
||||
className={`field-description-${path.replace(/\./g, '__')}`}
|
||||
description={description}
|
||||
path={path}
|
||||
// value={value}
|
||||
value={value}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</div>
|
||||
</TextInputWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
'use client'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from '../../../providers/Translation'
|
||||
import { useTranslation } from '../../../../providers/Translation'
|
||||
|
||||
import type { TextareaField, Validate } from 'payload/types'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import useField from '../../useField'
|
||||
|
||||
import './index.scss'
|
||||
import useField from '../../../useField'
|
||||
|
||||
export type TextAreaInputProps = Omit<TextareaField, 'type'> & {
|
||||
className?: string
|
||||
34
packages/ui/src/forms/field-types/Textarea/Wrapper/index.tsx
Normal file
34
packages/ui/src/forms/field-types/Textarea/Wrapper/index.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
import { fieldBaseClass } from '../../shared'
|
||||
import { useFormFields } from '../../../Form/context'
|
||||
|
||||
export const TextareaInputWrapper: React.FC<{
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
width?: string
|
||||
path?: string
|
||||
readOnly?: boolean
|
||||
}> = (props) => {
|
||||
const { children, className, style, width, path, readOnly } = props
|
||||
|
||||
const field = useFormFields(([fields]) => fields[path])
|
||||
|
||||
const { valid } = field || {}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'textarea', className, !valid && 'error', readOnly && 'read-only']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import React from 'react'
|
||||
import type { Props } from './types'
|
||||
import { fieldBaseClass, isFieldRTL } from '../shared'
|
||||
import { isFieldRTL } from '../shared'
|
||||
import TextareaInput from './Input'
|
||||
import DefaultError from '../../Error'
|
||||
import DefaultLabel from '../../Label'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import { TextareaInputWrapper } from './Wrapper'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const Textarea: React.FC<Props> = (props) => {
|
||||
@@ -27,11 +29,10 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
minLength,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
valid,
|
||||
errorMessage,
|
||||
value,
|
||||
locale,
|
||||
config: { localization },
|
||||
i18n,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -47,17 +48,20 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'textarea', className, !valid && 'error', readOnly && 'read-only']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
<TextareaInputWrapper
|
||||
className={className}
|
||||
readOnly={readOnly}
|
||||
style={style}
|
||||
width={width}
|
||||
path={path}
|
||||
>
|
||||
<ErrorComp message={errorMessage} showError={!valid} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp path={path} />
|
||||
<LabelComp
|
||||
htmlFor={`field-${path.replace(/\./g, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
i18n={i18n}
|
||||
/>
|
||||
<label className="textarea-outer" htmlFor={`field-${path.replace(/\./g, '__')}`}>
|
||||
<div className="textarea-inner">
|
||||
<div className="textarea-clone" data-value={value || placeholder || ''} />
|
||||
@@ -76,8 +80,8 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
{Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)}
|
||||
</div>
|
||||
</label>
|
||||
<FieldDescription description={description} path={path} value={value} />
|
||||
</div>
|
||||
<FieldDescription description={description} path={path} value={value} i18n={i18n} />
|
||||
</TextareaInputWrapper>
|
||||
)
|
||||
}
|
||||
export default Textarea
|
||||
|
||||
@@ -13,11 +13,11 @@ export type FormFieldBase = {
|
||||
valid?: boolean
|
||||
errorMessage?: string
|
||||
user?: User
|
||||
i18n: I18n
|
||||
payload: Payload
|
||||
docPreferences: DocumentPreferences
|
||||
i18n?: I18n
|
||||
payload?: Payload
|
||||
docPreferences?: DocumentPreferences
|
||||
locale?: Locale
|
||||
config: SanitizedConfig
|
||||
config?: SanitizedConfig
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,9 +24,11 @@ const useField = <T,>(options: Options): FieldType<T> => {
|
||||
const { user } = useAuth()
|
||||
const { id } = useDocumentInfo()
|
||||
const operation = useOperation()
|
||||
const field = useFormFields(([fields]) => fields[path])
|
||||
const { field, dispatchField } = useFormFields(([fields, dispatch]) => ({
|
||||
field: fields[path],
|
||||
dispatchField: dispatch,
|
||||
}))
|
||||
const { t } = useTranslation()
|
||||
const dispatchField = useFormFields(([_, dispatch]) => dispatch)
|
||||
const config = useConfig()
|
||||
|
||||
const { getData, getDataByPath, getSiblingData, setModified } = useForm()
|
||||
|
||||
56
packages/ui/src/views/Edit/action.ts
Normal file
56
packages/ui/src/views/Edit/action.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
'use server'
|
||||
|
||||
import { getPayload } from 'payload'
|
||||
import { FormState } from '../../forms/Form/types'
|
||||
import configPromise from 'payload-config'
|
||||
import buildStateFromSchema from '../../forms/Form/buildStateFromSchema'
|
||||
import { reduceFieldsToValues } from '../..'
|
||||
import { DocumentPreferences } from 'payload/types'
|
||||
import { Locale } from 'payload/config'
|
||||
import { User } from 'payload/auth'
|
||||
import { initTFunction } from '@payloadcms/translations'
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
|
||||
export const getFormStateFromServer = async (
|
||||
args: {
|
||||
collectionSlug: string
|
||||
docPreferences: DocumentPreferences
|
||||
locale: Locale
|
||||
id?: string
|
||||
operation: 'create' | 'update'
|
||||
user: User
|
||||
language: string
|
||||
},
|
||||
{
|
||||
formState,
|
||||
}: {
|
||||
formState: FormState
|
||||
},
|
||||
) => {
|
||||
const { collectionSlug, docPreferences, locale, id, operation, user, language } = args
|
||||
|
||||
const payload = await getPayload({
|
||||
config: configPromise,
|
||||
})
|
||||
|
||||
const collectionConfig = payload.collections[collectionSlug].config
|
||||
|
||||
const data = reduceFieldsToValues(formState, true)
|
||||
|
||||
// TODO: memoize the creation of this function based on language
|
||||
const t = initTFunction({ config: payload.config.i18n, language, translations })
|
||||
|
||||
const result = await buildStateFromSchema({
|
||||
id,
|
||||
config: payload.config,
|
||||
data,
|
||||
fieldSchema: collectionConfig.fields,
|
||||
locale: locale.code,
|
||||
operation,
|
||||
preferences: docPreferences,
|
||||
t,
|
||||
user,
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -15,7 +15,8 @@ import { SetStepNav } from './SetStepNav'
|
||||
// import { Upload } from '../Upload'
|
||||
import './index.scss'
|
||||
import { EditViewProps } from '../types'
|
||||
import { fieldTypes } from '../../exports'
|
||||
import { fieldTypes } from '../../forms/field-types'
|
||||
import { getFormStateFromServer } from './action'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
@@ -93,15 +94,26 @@ export const DefaultEditView: React.FC<EditViewProps> = async (props) => {
|
||||
// setViewActions(defaultActions)
|
||||
// }, [id, location.pathname, collectionConfig?.admin?.components?.views?.Edit, setViewActions])
|
||||
|
||||
const onChange = getFormStateFromServer.bind(null, {
|
||||
collectionSlug: collectionConfig?.slug,
|
||||
id: id || undefined,
|
||||
locale,
|
||||
language: i18n.language,
|
||||
operation,
|
||||
docPreferences,
|
||||
user,
|
||||
})
|
||||
|
||||
return (
|
||||
<main className={classes}>
|
||||
<OperationProvider operation={operation}>
|
||||
<Form
|
||||
action={action}
|
||||
// action={action}
|
||||
className={`${baseClass}__form`}
|
||||
disabled={!hasSavePermission}
|
||||
initialState={formState}
|
||||
method={id ? 'PATCH' : 'POST'}
|
||||
onChange={[onChange]}
|
||||
// onSuccess={onSave}
|
||||
>
|
||||
<FormLoadingOverlayToggle
|
||||
@@ -115,7 +127,6 @@ export const DefaultEditView: React.FC<EditViewProps> = async (props) => {
|
||||
}`}
|
||||
type="withoutNav"
|
||||
/>
|
||||
|
||||
{/* <Meta
|
||||
description={`${isEditing ? t('general:editing') : t('general:creating')} - ${getTranslation(
|
||||
collection.labels.singular,
|
||||
|
||||
@@ -27,7 +27,7 @@ export type EditViewProps = (
|
||||
}
|
||||
) & {
|
||||
config: SanitizedConfig
|
||||
action: string
|
||||
action?: string
|
||||
apiURL: string
|
||||
canAccessAdmin?: boolean
|
||||
data: Document
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
"emitDeclarationOnly": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"paths": {
|
||||
"payload-config": ["./src/config.ts"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
|
||||
Reference in New Issue
Block a user