@measured/puck 0.12.0-canary.39005a4 → 0.12.0-canary.53618f5

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # puck
1
+ # Puck
2
2
 
3
3
  The self-hosted, drag and drop editor for React.
4
4
 
@@ -17,25 +17,30 @@ The self-hosted, drag and drop editor for React.
17
17
  </a>
18
18
  </p>
19
19
 
20
- ## Features
20
+ ## Demo
21
21
 
22
- - 🖱️ **Drag and drop**: Visual editing for your existing React component library
23
- - 🌐 **Integrations**: Load your content from a 3rd party headless CMS
24
- - ✍️ **Inline editing**: Author content directly via puck for convenience
25
- - ⭐️ **No vendor lock-in**: Self-host or integrate with your existing application
22
+ Visit https://demo.puckeditor.com/edit to try the demo.
26
23
 
27
- [See demo](https://puck-editor-demo.vercel.app/edit)
24
+ ## Documentation
28
25
 
29
- ## Example
26
+ Visit https://puckeditor.com to view the full documentation.
27
+
28
+ ## Quick start
29
+
30
+ Install the package:
31
+
32
+ ```sh
33
+ npm i @measured/puck --save # or npx create-puck-app my-app
34
+ ```
30
35
 
31
36
  Render the editor:
32
37
 
33
38
  ```jsx
34
39
  // Editor.jsx
35
40
  import { Puck } from "@measured/puck";
36
- import "@measured/puck/dist/index.css";
41
+ import "@measured/puck/puck.css";
37
42
 
38
- // Create puck component config
43
+ // Create Puck component config
39
44
  const config = {
40
45
  components: {
41
46
  HeadingBlock: {
@@ -71,495 +76,25 @@ Render the page:
71
76
  ```jsx
72
77
  // Page.jsx
73
78
  import { Render } from "@measured/puck";
74
- import "@measured/puck/dist/index.css";
79
+ import "@measured/puck/puck.css";
75
80
 
76
81
  export function Page() {
77
82
  return <Render config={config} data={data} />;
78
83
  }
79
84
  ```
80
85
 
81
- ## Installation
82
-
83
- Install the package
84
-
85
- ```
86
- npm i @measured/puck --save
87
- ```
86
+ ## Recipes
88
87
 
89
- Or generate a puck application using a recipe
88
+ Use `create-puck-app` to quickly spin up a a pre-configured app based on our provided [recipes](https://github.com/measuredco/puck/tree/main/recipes):
90
89
 
91
90
  ```sh
92
91
  npx create-puck-app my-app
93
92
  ```
94
93
 
95
- ## Recipes
96
-
97
- Puck is a React component that can be easily integrated into your existing application. We also provide helpful recipes for common use cases:
98
-
99
- - [**next**](https://github.com/measuredco/puck/tree/main/recipes/next): Next.js app example
100
-
101
- ## Plugins
102
-
103
- Puck can be configured to work with plugins. Plugins can extend the functionality to support novel functionality.
104
-
105
- ### Official plugins
106
-
107
- - [`heading-analyzer`](https://github.com/measuredco/puck/tree/main/packages/plugin-heading-analyzer): Analyze the heading outline of your page and be warned when you're not respecting WCAG 2 accessibility standards.
108
-
109
- ### Developing a plugin
110
-
111
- The plugin API follows a React paradigm. Each plugin passed to the Puck editor can provide three functions:
112
-
113
- - `renderRoot` (`Component`): Render the root node of the preview content
114
- - `renderRootFields` (`Component`): Render the root fields
115
- - `renderFields` (`Component`): Render the fields for the currently selected component
116
- - `renderComponentList` (`Component`): Render the component list
117
-
118
- Each render function receives three props:
119
-
120
- - **children** (`ReactNode`): The normal contents of the root or field. You must render this if provided.
121
- - **state** (`AppState`): The current application state, including data and UI state
122
- - **dispatch** (`(action: PuckAction) => void`): The Puck dispatcher, used for making data changes or updating the UI. See the [action definitions](https://github.com/measuredco/puck/blob/main/packages/core/reducer/actions.tsx) for a full reference of available mutations.
123
-
124
- #### Example
125
-
126
- Here's an example plugin that creates a button to toggle the left side-bar:
127
-
128
- ```jsx
129
- const myPlugin = {
130
- renderRootFields: ({ children, dispatch, state }) => (
131
- <div>
132
- {children}
133
-
134
- <button
135
- onClick={() => {
136
- dispatch({
137
- type: "setUi",
138
- ui: { leftSideBarVisible: !state.ui.leftSideBarVisible },
139
- });
140
- }}
141
- >
142
- Toggle side-bar
143
- </button>
144
- </div>
145
- ),
146
- };
147
- ```
148
-
149
- ## Custom fields
150
-
151
- Puck supports custom fields using the `custom` field type and `render` method.
152
-
153
- In this example, we optionally add the `<FieldLabel>` component to add a label:
154
-
155
- ```tsx
156
- import { FieldLabel } from "@measured/puck";
157
-
158
- export const MyComponent: ComponentConfig = {
159
- fields: {
160
- myField: {
161
- type: "custom",
162
- render: ({ field, name, onChange, value }) => {
163
- return (
164
- <FieldLabel label={field.label || name}>
165
- <input
166
- placeholder="Enter text..."
167
- type="text"
168
- name={name}
169
- defaultValue={value}
170
- onChange={(e) => onChange(e.currentTarget.value)}
171
- ></input>
172
- </FieldLabel>
173
- );
174
- },
175
- },
176
- },
177
- };
178
- ```
179
-
180
- ## DropZones
181
-
182
- Puck supports creating complex layouts (like multi-column layouts) using the `<DropZone>` component.
183
-
184
- ### Example
185
-
186
- In this example, we use the `<DropZone>` component to render two nested DropZones within another component:
187
-
188
- ```tsx
189
- import { DropZone } from "@measured/puck";
190
-
191
- export const MyComponent: ComponentConfig = {
192
- render: () => {
193
- return (
194
- <div>
195
- <DropZone zone="first-drop-zone">
196
- <DropZone zone="second-drop-zone">
197
- </div>
198
- )
199
- }
200
- };
201
- ```
202
-
203
- ### Custom root entry points
204
-
205
- You can also do this at the root of your component. This is useful if you have a fixed layout and only want to make certain parts of your page customisable:
206
-
207
- ```tsx
208
- import { DropZone, Config } from "@measured/puck";
209
-
210
- export const config: Config = {
211
- root: {
212
- render: ({ children }) => {
213
- return (
214
- <div>
215
- {/* children renders the default zone. This can be omitted if necessary. */}
216
- {children}
217
-
218
- <div>
219
- <DropZone zone="other-drop-zone">
220
- </div>
221
- </div>
222
- )
223
- }
224
- }
225
- };
226
- ```
227
-
228
- ### The Rules of DropZones
229
-
230
- The current DropZone implementation has certain rules and limitations:
231
-
232
- 1. You can drag from the component list on the LHS into any DropZone
233
- 2. You can drag components between DropZones, so long as those DropZones share a parent (also known as _area_)
234
- 3. You can't drag between DropZones that don't share a parent (or _area_)
235
- 4. Your mouse must be directly over a DropZone for a collision to be detected
236
-
237
- ## External fields
238
-
239
- External fields can be used to import data from a third-party API, such as a headless CMS.
240
-
241
- ### Example
242
-
243
- The `external` field type enables us to query data from a third party API:
244
-
245
- ```tsx
246
- const config = {
247
- components: {
248
- HeadingBlock: {
249
- fields: {
250
- myData: {
251
- type: "external",
252
- fetchList: async () => {
253
- const response = await fetch("https://www.example.com/api");
254
-
255
- return {
256
- text: response.json().text,
257
- };
258
- },
259
- },
260
- },
261
- render: ({ myData }) => {
262
- return <h1>{myData.text}</h1>;
263
- },
264
- },
265
- },
266
- };
267
- ```
268
-
269
- When the user interacts with this external field, they'll be presented with a list of items to choose from. Once they select an item, the value will be mapped onto the prop. In this case, `myData`.
270
-
271
- ## Dynamic prop resolution
272
-
273
- Dynamic prop resolution allows developers to change the props for a component after the props have been changed by the user. This is useful for making third-party API calls, such as requesting the latest content from a headless CMS.
274
-
275
- ### resolveData()
276
-
277
- `resolveData` is defined in the component config, and allows the developer to make asynchronous calls to change the [ComponentData](#componentdata) after they've been set by Puck. Receives [ComponentData](#componentdata) and returns [ComponentData](#componentdata).
278
-
279
- #### Examples
280
-
281
- ##### Basic example
282
-
283
- In this example, we remap the `text` prop to the `title` prop and mark the `title` field as read-only.
284
-
285
- ```tsx
286
- const config = {
287
- components: {
288
- HeadingBlock: {
289
- fields: {
290
- text: {
291
- type: "text",
292
- },
293
- title: {
294
- type: "text",
295
- },
296
- },
297
- resolveData: async (props) => {
298
- return {
299
- props: {
300
- title: props.text,
301
- },
302
- readOnly: {
303
- title: true,
304
- },
305
- };
306
- },
307
- render: ({ title }) => {
308
- return <h1>{title}</h1>;
309
- },
310
- },
311
- },
312
- };
313
- ```
314
-
315
- ##### Combining with external fields
316
-
317
- A more advanced pattern is to combine the `resolveData` method with `external` fields to dynamically fetch data when rendering the component.
318
-
319
- ```tsx
320
- const config = {
321
- components: {
322
- HeadingBlock: {
323
- fields: {
324
- myData: {
325
- type: "external",
326
- placeholder: "Select from example.com",
327
- fetchList: async () => {
328
- const response = await fetch("https://www.example.com/api");
329
-
330
- return {
331
- id: response.json().id,
332
- };
333
- },
334
- },
335
- title: {
336
- type: "text",
337
- },
338
- },
339
- resolveData: async (props) => {
340
- if (!myData.id) {
341
- return { props, readOnly: { title: false } };
342
- }
343
-
344
- const latestData = await fetch(
345
- `https://www.example.com/api/${myData.id}`
346
- );
347
-
348
- return {
349
- props: {
350
- title: latestData.json().text,
351
- },
352
- readOnly: {
353
- title: true,
354
- },
355
- };
356
- },
357
- render: ({ title }) => {
358
- return <h1>{title}</h1>;
359
- },
360
- },
361
- },
362
- };
363
- ```
364
-
365
- ### resolveAllData()
366
-
367
- `resolveAllData` is a utility function exported by Puck to enable the developer to run all their `resolveData` methods before rendering the component with `<Render>`.
368
-
369
- If your `resolveData` methods rely on any external APIs, you should run this before rendering your page.
370
-
371
- ```tsx
372
- import { resolveAllData } from "@measured/puck";
373
-
374
- const resolvedData = resolveAllData(data, config);
375
- ```
376
-
377
- ### React server components
378
-
379
- If you want to use React server components, use ` <Render>` from the `@measured/puck/rsc` bundle instead of the main bundle.
380
-
381
- ```tsx
382
- import { Render } from "@measured/puck/rsc";
383
- import "@measured/puck/dist/index.css";
384
-
385
- export function Page() {
386
- return <Render config={config} data={data} />;
387
- }
388
- ```
389
-
390
- If you're using DropZones with React server components, use the `puck.renderDropZone` prop provided to your render function instead of the `<DropZone>` component.
391
-
392
- ```tsx
393
- export const MyComponent: ComponentConfig = {
394
- render: ({ puck: { renderDropZone } }) => {
395
- return (
396
- <div>
397
- {renderDropZone({ zone: "first-drop-zone" })}
398
- {renderDropZone({ zone: "second-drop-zone" })}
399
- </div>
400
- );
401
- },
402
- };
403
- ```
404
-
405
- In future, we may deprecate DropZone in favour of renderDropZone.
406
-
407
- ## Reference
408
-
409
- ### `<Puck>`
410
-
411
- The `<Puck>` component renders the Puck editor.
412
-
413
- - **config** (`Config`): Puck component configuration
414
- - **data** (`Data`): Initial data to render
415
- - **onChange** (`(Data) => void` [optional]): Callback that triggers when the user makes a change
416
- - **onPublish** (`(Data) => void` [optional]): Callback that triggers when the user hits the "Publish" button
417
- - **renderComponentList** (`Component` [optional]): Render function for wrapping the component list
418
- - **renderHeader** (`Component` [optional]): Render function for overriding the Puck header component
419
- - **renderHeaderActions** (`Component` [optional]): Render function for overriding the Puck header actions. Use a fragment.
420
- - **headerTitle** (`string` [optional]): Set the title shown in the header title
421
- - **headerPath** (`string` [optional]): Set a path to show after the header title
422
- - **plugins** (`Plugin[]` [optional]): Array of plugins that can be used to enhance Puck
423
-
424
- ### `<Render>`
425
-
426
- The `<Render>` component renders user-facing UI using Puck data.
427
-
428
- - **config** (`Config`): Puck component configuration
429
- - **data** (`Data`): Data to render
430
-
431
- ### `<DropZone>`
432
-
433
- The `<DropZone>` component allows you to create advanced layouts, like multi-columns.
434
-
435
- - **zone** (`string`): Identifier for the zone of your component, unique to the parent component
436
- - **style** (`CSSProperties`): Custom inline styles
437
-
438
- ### `Config`
439
-
440
- The `Config` object describes which components Puck should render, how they should render and which inputs are available to them.
441
-
442
- - **root** (`object`)
443
- - **fields** (`object`):
444
- - **title** (`Field`): Title of the content, typically used for the page title.
445
- - **[fieldName]** (`Field`): User defined fields, used to describe the input data stored in the `root` key.
446
- - **render** (`Component`): Render a React component at the root of your component tree. Useful for defining context providers.
447
- - **resolveData** (`async (data: ComponentData) => ComponentData` [optional]): Function to dynamically change props before rendering the root.
448
- - **components** (`object`): Definitions for each of the components you want to show in the visual editor
449
- - **[componentName]** (`object`)
450
- - **fields** (`Field`): The Field objects describing the input data stored against this component.
451
- - **render** (`Component`): Render function for your React component. Receives props as defined in fields.
452
- - **defaultProps** (`object` [optional]): Default props to pass to your component. Will show in fields.
453
- - **resolveData** (`async (data: ComponentData) => ComponentData` [optional]): Function to dynamically change props before rendering the component.
454
- - **categories** (`object`): Component categories for rendering in the side bar or restricting in DropZones
455
- - **[categoryName]** (`object`)
456
- - **components** (`sting[]`, [optional]): Array containing the names of components in this category
457
- - **title** (`sting`, [optional]): Title of the category
458
- - **visible** (`boolean`, [optional]): Whether or not the category should be visible in the side bar
459
- - **defaultExpanded** (`boolean`, [optional]): Whether or not the category should be expanded in the side bar by default
460
-
461
- ### `Field`
462
-
463
- A `Field` represents a user input field shown in the Puck interface.
464
-
465
- ### All Fields
466
-
467
- - **label** (`text` [optional]): A label for the input. Will use the key if not provided.
468
-
469
- ### Text Fields
470
-
471
- - **type** (`"text"`)
472
-
473
- ### Textarea Fields
474
-
475
- - **type** (`"textarea"`)
476
-
477
- ### Number Fields
478
-
479
- - **type** (`"number"`)
480
-
481
- ### Select Fields
482
-
483
- - **type** (`"select"`)
484
- - **options** (`object[]`): array of items to render
485
- - **label** (`string`)
486
- - **value** (`string` | `number` | `boolean`)
487
-
488
- ### Radio Fields
489
-
490
- - **type** (`"radio"`)
491
- - **options** (`object[]`): array of items to render
492
- - **label** (`string`)
493
- - **value** (`string` | `number` | `boolean`)
494
-
495
- ### Array Fields
496
-
497
- - **type** (`"array"`)
498
- - **arrayFields** (`object`): Object describing sub-fields for each item
499
- - **[fieldName]** (`Field`): The Field objects describing the input data for each item
500
- - **getItemSummary** (`(object, number) => string` [optional]): Function to get the label of each item
501
- - **defaultItemProps** (`object` [optional]): Default props to pass to each new item added, when using a `array` field type
502
-
503
- ### External Fields
504
-
505
- External fields can be used to load content from an external content repository, like Strapi.js.
506
-
507
- - **type** (`"external"`)
508
- - **placeholder** (`string`): A placeholder for the external field button
509
- - **fetchList** (`() => object`): Fetch content from a third-party API and return an array
510
- - **mapProp** (`(selectedItem: object) => object`): Map the selected item into another shape
511
-
512
- ### Custom Fields
513
-
514
- - **type** (`"custom"`)
515
- - **render** (`Component`): Render a custom field. Receives the props:
516
- - **field** (`Field`): Field configuration
517
- - **name** (`string`): Name of the field
518
- - **value** (`any`): Value for the field
519
- - **onChange** (`(value: any) => void`): Callback to change the value
520
- - **readOnly** (`boolean` | `undefined`): Whether or not the field should be in readOnly mode
521
-
522
- ### `AppState`
523
-
524
- The `AppState` object stores the puck application state.
525
-
526
- - **data** (`Data`): The page data currently being rendered
527
- - **ui** (`object`):
528
- - **leftSideBarVisible** (boolean): Whether or not the left side bar is visible
529
- - **itemSelector** (object): An object describing which item is selected
530
- - **arrayState** (object): An object describing the internal state of array items
531
- - **componentList** (object): An object describing the component list. Similar shape to `Config.categories`.
532
- - **components** (`sting[]`, [optional]): Array containing the names of components in this category
533
- - **title** (`sting`, [optional]): Title of the category
534
- - **visible** (`boolean`, [optional]): Whether or not the category is visible in the side bar
535
- - **expanded** (`boolean`, [optional]): Whether or not the category is expanded in the side bar
536
-
537
- ### `Data`
538
-
539
- The `Data` object stores the puck page data.
540
-
541
- - **root** (`ComponentData`): The component data for the root of your configuration.
542
- - **props** (object): Extends `ComponentData.props`, with some additional props
543
- - **title** (`string`, [optional]): Title of the content, typically used for the page title
544
- - **content** (`ComponentData[]`): Component data for the main content
545
- - **zones** (`object`, [optional]): Component data for all DropZones
546
- **[zoneCompound]** (`ComponentData[]`): Component data for a specific DropZone `zone` within a component instance
547
-
548
- ### `ComponentData`
549
-
550
- - **type** (`string`): Component name
551
- - **props** (`object`):
552
- - **[prop]** (`any`): User defined data from component fields
553
- - **readOnly** (`object`): Object describing which fields on the component are currently read-only. Can use dot-notation for arrays, like `array[1].text` or `array[*].text`.
554
- - **[prop]** (`boolean`): boolean describing whether or not the prop field is read-only
555
-
556
- ### `Plugin`
557
-
558
- Plugins that can be used to enhance Puck.
94
+ Available recipes include:
559
95
 
560
- - **renderRoot** (`Component`): Render the root node of the preview content
561
- - **renderRootFields** (`Component`): Render the root fields
562
- - **renderFields** (`Component`): Render the fields for the currently selected component
96
+ - [**next**](https://github.com/measuredco/puck/tree/main/recipes/next): Next.js 13 app example, using App Router and static page generation
97
+ - [**remix**](https://github.com/measuredco/puck/tree/main/recipes/remix): Remix Run v2 app example, using dynamic routes at root-level
563
98
 
564
99
  ## Hire the Puck team
565
100
 
@@ -170,7 +170,7 @@ type Data<Props extends DefaultComponentProps = DefaultComponentProps, RootProps
170
170
  };
171
171
  type ItemWithId = {
172
172
  _arrayId: string;
173
- data: any;
173
+ _originalIndex: number;
174
174
  };
175
175
  type ArrayState = {
176
176
  items: ItemWithId[];
@@ -178,6 +178,7 @@ type ArrayState = {
178
178
  };
179
179
  type UiState = {
180
180
  leftSideBarVisible: boolean;
181
+ rightSideBarVisible: boolean;
181
182
  itemSelector: ItemSelector | null;
182
183
  arrayState: Record<string, ArrayState | undefined>;
183
184
  componentList: Record<string, {