Skip to content

Commit

Permalink
chore(next): ssr collapsible field (#4894)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobsfletch authored and DanRibbens committed Feb 27, 2024
1 parent 64ff9d9 commit 5691760
Show file tree
Hide file tree
Showing 17 changed files with 274 additions and 194 deletions.
15 changes: 15 additions & 0 deletions packages/dev/src/collections/Pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,21 @@ export const Pages: CollectionConfig = {
type: 'code',
required: true,
},
{
label: ({ data }) => `This is ${data?.title || 'Untitled'}`,
type: 'collapsible',
admin: {
initCollapsed: true,
},
fields: [
{
name: 'collapsibleText',
label: 'Collapsible Text',
type: 'text',
required: true,
},
],
},
{
name: 'group',
label: 'Group',
Expand Down
5 changes: 5 additions & 0 deletions packages/next/src/utilities/createClientConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const sanitizeField = (f) => {
if ('hooks' in field) delete field.hooks
if ('validate' in field) delete field.validate
if ('defaultValue' in field) delete field.defaultValue
if ('label' in field) delete field.label

if ('fields' in field) {
field.fields = sanitizeFields(field.fields)
Expand All @@ -26,6 +27,10 @@ export const sanitizeField = (f) => {
if ('condition' in field.admin) {
delete field.admin.condition
}

if ('description' in field.admin) {
delete field.admin.description
}
}

return field
Expand Down
42 changes: 14 additions & 28 deletions packages/ui/src/forms/RowLabel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,16 @@
'use client'
import React from 'react'
import { useTranslation } from '../../providers/Translation'

import type { Props } from './types'

import { Props, isComponent } from './types'
import getSiblingData from '../Form/getSiblingData'
import getDataByPath from '../Form/getDataByPath'
import { getTranslation } from '@payloadcms/translations'
import { useWatchForm } from '../Form/context'
import { isComponent } from './types'

const baseClass = 'row-label'

export const RowLabel: React.FC<Props> = ({ className, ...rest }) => {
return (
<span
className={[baseClass, className].filter(Boolean).join(' ')}
style={{
pointerEvents: 'none',
}}
>
<RowLabelContent {...rest} />
</span>
)
}

const RowLabelContent: React.FC<Omit<Props, 'className'>> = (props) => {
const { label, path, rowNumber } = props
export const RowLabel: React.FC<Props> = (props) => {
const { className, label, path, rowNumber, data: dataFromProps, i18n } = props

const { i18n } = useTranslation()
const { getDataByPath, getSiblingData } = useWatchForm()
const collapsibleData = getSiblingData(path)
const arrayData = getDataByPath(path)
const collapsibleData = getSiblingData(dataFromProps, path)
const arrayData = getDataByPath(dataFromProps, path)
const data = arrayData || collapsibleData

if (isComponent(label)) {
Expand All @@ -38,14 +19,19 @@ const RowLabelContent: React.FC<Omit<Props, 'className'>> = (props) => {
}

return (
<React.Fragment>
<span
className={[baseClass, className].filter(Boolean).join(' ')}
style={{
pointerEvents: 'none',
}}
>
{typeof label === 'function'
? label({
data,
index: rowNumber,
path,
})
: getTranslation(label, i18n)}
</React.Fragment>
</span>
)
}
4 changes: 4 additions & 0 deletions packages/ui/src/forms/RowLabel/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import React from 'react'

import { RowLabel, RowLabelComponent } from 'payload/types'
import { FormState } from '../..'
import { I18n } from '@payloadcms/translations'

export type Props = {
className?: string
label?: RowLabel
path: string
rowNumber?: number
data: FormState
i18n: I18n
}

export function isComponent(label: RowLabel): label is RowLabelComponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const getNestedFieldState = ({
}): {
errorCount: number
fieldState: FormState
pathSegments: string[]
} => {
let pathSegments = pathSegmentsFromProps

Expand All @@ -25,5 +26,8 @@ export const getNestedFieldState = ({

const result = getFieldStateFromPaths({ formState, pathSegments })

return result
return {
...result,
pathSegments,
}
}
19 changes: 2 additions & 17 deletions packages/ui/src/forms/WatchChildErrors/index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,21 @@
'use client'
import React from 'react'

import type { Field } from 'payload/types'

import useThrottledEffect from '../../hooks/useThrottledEffect'
import { useAllFormFields, useFormSubmitted } from '../Form/context'
import { buildPathSegments } from './buildPathSegments'
import { getFieldStateFromPaths } from './getFieldStateFromPaths'

type TrackSubSchemaErrorCountProps = {
/**
* Only for collapsibles, and unnamed-tabs
*/
fieldSchema?: Field[]
path: string
pathSegments?: string[]
setErrorCount: (count: number) => void
}

export const WatchChildErrors: React.FC<TrackSubSchemaErrorCountProps> = ({
fieldSchema,
path,
pathSegments,
setErrorCount,
}) => {
const [formState] = useAllFormFields()
const hasSubmitted = useFormSubmitted()
const [pathSegments] = React.useState(() => {
if (fieldSchema) {
return buildPathSegments(path, fieldSchema)
}

return [`${path}.`]
})

useThrottledEffect(
() => {
Expand Down
4 changes: 3 additions & 1 deletion packages/ui/src/forms/field-types/Code/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export const CodeInput: React.FC<{

const memoizedValidate = useCallback(
(value, options) => {
return validate(value, { ...options, required })
if (typeof validate === 'function') {
return validate(value, { ...options, required })
}
},
[validate, required],
)
Expand Down
79 changes: 79 additions & 0 deletions packages/ui/src/forms/field-types/Collapsible/Input/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client'
import React, { Fragment, useCallback, useState } from 'react'

import type { DocumentPreferences } from 'payload/types'

import { Collapsible } from '../../../../elements/Collapsible'
import { ErrorPill } from '../../../../elements/ErrorPill'
import { useDocumentInfo } from '../../../../providers/DocumentInfo'
import { usePreferences } from '../../../../providers/Preferences'
import { useTranslation } from '../../../..'
import { WatchChildErrors } from '../../../WatchChildErrors'

export const CollapsibleInput: React.FC<{
initCollapsed?: boolean
children: React.ReactNode
path: string
baseClass: string
RowLabel?: React.ReactNode
fieldPreferencesKey?: string
pathSegments?: string[]
}> = (props) => {
const { initCollapsed, children, path, baseClass, RowLabel, fieldPreferencesKey, pathSegments } =
props

const { getPreference, setPreference } = usePreferences()
const { preferencesKey } = useDocumentInfo()
const [errorCount, setErrorCount] = useState(0)
const { i18n } = useTranslation()

const onToggle = useCallback(
async (newCollapsedState: boolean) => {
const existingPreferences: DocumentPreferences = await getPreference(preferencesKey)

setPreference(preferencesKey, {
...existingPreferences,
...(path
? {
fields: {
...(existingPreferences?.fields || {}),
[path]: {
...existingPreferences?.fields?.[path],
collapsed: newCollapsedState,
},
},
}
: {
fields: {
...(existingPreferences?.fields || {}),
[fieldPreferencesKey]: {
...existingPreferences?.fields?.[fieldPreferencesKey],
collapsed: newCollapsedState,
},
},
}),
})
},
[preferencesKey, fieldPreferencesKey, getPreference, setPreference, path],
)

return (
<Fragment>
<WatchChildErrors pathSegments={pathSegments} setErrorCount={setErrorCount} />
<Collapsible
className={`${baseClass}__collapsible`}
collapsibleStyle={errorCount > 0 ? 'error' : 'default'}
header={
<div className={`${baseClass}__row-label-wrap`}>
{RowLabel}
{errorCount > 0 && <ErrorPill count={errorCount} withMessage i18n={i18n} />}
</div>
}
initCollapsed={initCollapsed}
onToggle={onToggle}
>
{children}
</Collapsible>
</Fragment>
)
}
36 changes: 36 additions & 0 deletions packages/ui/src/forms/field-types/Collapsible/Wrapper/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client'
import React from 'react'

import { fieldBaseClass } from '../../shared'
import { useFormFields } from '../../../Form/context'

const baseClass = 'collapsible-field'

export const CollapsibleFieldWrapper: React.FC<{
className?: string
path: string
children: React.ReactNode
id?: string
}> = (props) => {
const { children, className, path, id } = props

const field = useFormFields(([fields]) => fields[path])

const { valid } = field || {}

return (
<div
className={[
fieldBaseClass,
baseClass,
className,
!valid ? `${baseClass}--has-error` : `${baseClass}--has-no-error`,
]
.filter(Boolean)
.join(' ')}
id={id}
>
{children}
</div>
)
}
Loading

0 comments on commit 5691760

Please sign in to comment.