@@ -40,13 +40,19 @@ interface ContentFormProps {
4040 schema : Schema ;
4141 heading : ReactNode ;
4242 submitMethod : 'post' | 'patch' ;
43+ /** Per-tab sticky panels rendered left of the form (eg. translation source). */
44+ asidePanels ?: { header ?: ReactNode ; blocks ?: ReactNode ; content ?: ReactNode } ;
45+ /** Placeholder text per field name (eg. the source values of a translation). */
46+ fieldPlaceholders ?: Record < string , string > ;
4347}
4448
4549export default function ContentForm ( {
4650 content,
4751 schema,
4852 heading,
4953 submitMethod,
54+ asidePanels,
55+ fieldPlaceholders,
5056} : ContentFormProps ) {
5157 const { t } = useTranslation ( ) ;
5258 const fetcher = useFetcher ( ) ;
@@ -64,6 +70,68 @@ export default function ContentForm({
6470 } ,
6571 } ) ;
6672
73+ const fieldsetsForm = (
74+ < form >
75+ { schema . fieldsets . map ( ( fieldset ) => (
76+ < Accordion defaultExpandedKeys = { [ 'default' ] } key = { fieldset . id } >
77+ < AccordionItem id = { fieldset . id } key = { fieldset . id } >
78+ < AccordionItemTrigger > { fieldset . title } </ AccordionItemTrigger >
79+ < AccordionPanel >
80+ { ( fieldset . fields as DeepKeys < Content > [ ] ) . map (
81+ ( schemaField , index ) => (
82+ < form . AppField
83+ name = { schemaField }
84+ key = { index }
85+ // eslint-disable-next-line react/no-children-prop
86+ children = { ( field ) => (
87+ < field . Quanta
88+ { ...schema . properties [ schemaField ] }
89+ className = "mb-4"
90+ label = { schema . properties [ field . name ] . title }
91+ name = { field . name }
92+ defaultValue = { field . state . value }
93+ required = { schema . required . indexOf ( schemaField ) !== - 1 }
94+ error = { field . state . meta . errors }
95+ formAtom = { formAtom }
96+ value = { field . state . value }
97+ { ...( fieldPlaceholders ?. [ field . name ]
98+ ? { placeholder : fieldPlaceholders [ field . name ] }
99+ : { } ) }
100+ />
101+ ) }
102+ />
103+ ) ,
104+ ) }
105+ </ AccordionPanel >
106+ </ AccordionItem >
107+ </ Accordion >
108+ ) ) }
109+ </ form >
110+ ) ;
111+
112+ // The shared header row keeps both columns starting level; px-6 mirrors the
113+ // blocks editor's own gutters, padBody pads bodies that lack them.
114+ const withAside = ( panel : ReactNode , body : ReactNode , padBody = false ) =>
115+ asidePanels ? (
116+ < div
117+ className = { `
118+ grid grid-cols-1 gap-x-8 pt-4
119+ lg:grid-cols-2 lg:grid-rows-[auto_1fr]
120+ ` }
121+ >
122+ < div className = "mb-4 self-end px-6" > { asidePanels . header } </ div >
123+ < h1 className = "mb-4 self-end px-6 text-2xl font-bold" > { heading } </ h1 >
124+ < aside className = "sticky top-0 max-h-screen self-start overflow-y-auto px-6" >
125+ { panel }
126+ </ aside >
127+ < div className = { padBody ? 'flex flex-col px-6' : 'flex flex-col' } >
128+ { body }
129+ </ div >
130+ </ div >
131+ ) : (
132+ body
133+ ) ;
134+
67135 return (
68136 < Provider store = { store } >
69137 < InitAtoms atomValues = { [ [ formAtom , content ] ] } >
@@ -86,59 +154,22 @@ export default function ContentForm({
86154 {
87155 id : 'blocks' ,
88156 title : t ( 'cmsui.blocksEditor.blocksTab' ) ,
89- content : < BlocksEditor /> ,
157+ content : withAside ( asidePanels ?. blocks , < BlocksEditor /> ) ,
90158 } ,
91159 {
92160 id : 'content' ,
93161 title : t ( 'cmsui.blocksEditor.contentTab' ) ,
94- content : (
95- < div className = "flex flex-col" >
96- < h1 className = "mb-4 text-2xl font-bold" > { heading } </ h1 >
97- < form >
98- { schema . fieldsets . map ( ( fieldset ) => (
99- < Accordion
100- defaultExpandedKeys = { [ 'default' ] }
101- key = { fieldset . id }
102- >
103- < AccordionItem id = { fieldset . id } key = { fieldset . id } >
104- < AccordionItemTrigger >
105- { fieldset . title }
106- </ AccordionItemTrigger >
107- < AccordionPanel >
108- { ( fieldset . fields as DeepKeys < Content > [ ] ) . map (
109- ( schemaField , index ) => (
110- < form . AppField
111- name = { schemaField }
112- key = { index }
113- // eslint-disable-next-line react/no-children-prop
114- children = { ( field ) => (
115- < field . Quanta
116- { ...schema . properties [ schemaField ] }
117- className = "mb-4"
118- label = {
119- schema . properties [ field . name ] . title
120- }
121- name = { field . name }
122- defaultValue = { field . state . value }
123- required = {
124- schema . required . indexOf (
125- schemaField ,
126- ) !== - 1
127- }
128- error = { field . state . meta . errors }
129- formAtom = { formAtom }
130- value = { field . state . value }
131- />
132- ) }
133- />
134- ) ,
135- ) }
136- </ AccordionPanel >
137- </ AccordionItem >
138- </ Accordion >
139- ) ) }
140- </ form >
141- </ div >
162+ content : withAside (
163+ asidePanels ?. content ,
164+ asidePanels ? (
165+ fieldsetsForm
166+ ) : (
167+ < div className = "flex flex-col" >
168+ < h1 className = "mb-4 text-2xl font-bold" > { heading } </ h1 >
169+ { fieldsetsForm }
170+ </ div >
171+ ) ,
172+ true ,
142173 ) ,
143174 } ,
144175 ] }
0 commit comments