@measured/puck 0.11.0-canary.6145c32 → 0.11.0-canary.7f13efc

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # puck
2
2
 
3
+ The self-hosted, drag and drop editor for React.
4
+
3
5
  <p align="left">
4
6
  <a aria-label="Measured logo" href="https://measured.co">
5
7
  <img src="https://img.shields.io/badge/MADE%20BY%20Measured-000000.svg?style=for-the-badge&labelColor=000">
@@ -15,7 +17,7 @@
15
17
  </a>
16
18
  </p>
17
19
 
18
- The self-hosted, drag and drop editor for React.
20
+ ## Features
19
21
 
20
22
  - 🖱️ **Drag and drop**: Visual editing for your existing React component library
21
23
  - 🌐 **Integrations**: Load your content from a 3rd party headless CMS
@@ -111,10 +113,11 @@ The plugin API follows a React paradigm. Each plugin passed to the Puck editor c
111
113
  - `renderRoot` (`Component`): Render the root node of the preview content
112
114
  - `renderRootFields` (`Component`): Render the root fields
113
115
  - `renderFields` (`Component`): Render the fields for the currently selected component
116
+ - `renderComponentList` (`Component`): Render the component list
114
117
 
115
118
  Each render function receives three props:
116
119
 
117
- - **children** (`ReactNode`): The normal contents of the root or field. You must render this.
120
+ - **children** (`ReactNode`): The normal contents of the root or field. You must render this if provided.
118
121
  - **state** (`AppState`): The current application state, including data and UI state
119
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.
120
123
 
@@ -231,6 +234,146 @@ The current DropZone implementation has certain rules and limitations:
231
234
  3. You can't drag between DropZones that don't share a parent (or _area_)
232
235
  4. Your mouse must be directly over a DropZone for a collision to be detected
233
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 resolve props for components without saving the data to the Puck data model.
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
+
234
377
  ## Reference
235
378
 
236
379
  ### `<Puck>`
@@ -241,6 +384,7 @@ The `<Puck>` component renders the Puck editor.
241
384
  - **data** (`Data`): Initial data to render
242
385
  - **onChange** (`(Data) => void` [optional]): Callback that triggers when the user makes a change
243
386
  - **onPublish** (`(Data) => void` [optional]): Callback that triggers when the user hits the "Publish" button
387
+ - **renderComponentList** (`Component` [optional]): Render function for wrapping the component list
244
388
  - **renderHeader** (`Component` [optional]): Render function for overriding the Puck header component
245
389
  - **renderHeaderActions** (`Component` [optional]): Render function for overriding the Puck header actions. Use a fragment.
246
390
  - **headerTitle** (`string` [optional]): Set the title shown in the header title
@@ -270,27 +414,74 @@ The `Config` object describes which components Puck should render, how they shou
270
414
  - **title** (`Field`): Title of the content, typically used for the page title.
271
415
  - **[fieldName]** (`Field`): User defined fields, used to describe the input data stored in the `root` key.
272
416
  - **render** (`Component`): Render a React component at the root of your component tree. Useful for defining context providers.
417
+ - **resolveData** (`async (data: ComponentData) => ComponentData` [optional]): Function to dynamically change props before rendering the root.
273
418
  - **components** (`object`): Definitions for each of the components you want to show in the visual editor
274
419
  - **[componentName]** (`object`)
275
420
  - **fields** (`Field`): The Field objects describing the input data stored against this component.
276
421
  - **render** (`Component`): Render function for your React component. Receives props as defined in fields.
277
422
  - **defaultProps** (`object` [optional]): Default props to pass to your component. Will show in fields.
423
+ - **resolveData** (`async (data: ComponentData) => ComponentData` [optional]): Function to dynamically change props before rendering the component.
424
+ - **categories** (`object`): Component categories for rendering in the side bar or restricting in DropZones
425
+ - **[categoryName]** (`object`)
426
+ - **components** (`sting[]`, [optional]): Array containing the names of components in this category
427
+ - **title** (`sting`, [optional]): Title of the category
428
+ - **visible** (`boolean`, [optional]): Whether or not the category should be visible in the side bar
429
+ - **defaultExpanded** (`boolean`, [optional]): Whether or not the category should be expanded in the side bar by default
278
430
 
279
431
  ### `Field`
280
432
 
281
433
  A `Field` represents a user input field shown in the Puck interface.
282
434
 
283
- - **type** (`text` | `textarea` | `number` | `select` | `radio` | `external` | `array` | `custom`): The input type to render
435
+ ### All Fields
436
+
284
437
  - **label** (`text` [optional]): A label for the input. Will use the key if not provided.
285
- - **arrayFields** (`object`): Object describing sub-fields for items in an `array` input
286
- - **[fieldName]** (`Field`): The Field objects describing the input data for each item
287
- - **getItemSummary** (`(object, number) => string` [optional]): Function to get the name of each item when using the `array` or `external` field types
288
- - **defaultItemProps** (`object` [optional]): Default props to pass to each new item added, when using a `array` field type
289
- - **options** (`object[]`): array of items to render for select or radio inputs
438
+
439
+ ### Text Fields
440
+
441
+ - **type** (`"text"`)
442
+
443
+ ### Textarea Fields
444
+
445
+ - **type** (`"textarea"`)
446
+
447
+ ### Number Fields
448
+
449
+ - **type** (`"number"`)
450
+
451
+ ### Select Fields
452
+
453
+ - **type** (`"select"`)
454
+ - **options** (`object[]`): array of items to render
290
455
  - **label** (`string`)
291
456
  - **value** (`string` | `number` | `boolean`)
292
- - **adaptor** (`Adaptor`): Content adaptor if using the `external` input type
293
- - **adaptorParams** (`object`): Paramaters passed to the adaptor
457
+
458
+ ### Radio Fields
459
+
460
+ - **type** (`"radio"`)
461
+ - **options** (`object[]`): array of items to render
462
+ - **label** (`string`)
463
+ - **value** (`string` | `number` | `boolean`)
464
+
465
+ ### Array Fields
466
+
467
+ - **type** (`"array"`)
468
+ - **arrayFields** (`object`): Object describing sub-fields for each item
469
+ - **[fieldName]** (`Field`): The Field objects describing the input data for each item
470
+ - **getItemSummary** (`(object, number) => string` [optional]): Function to get the label of each item
471
+ - **defaultItemProps** (`object` [optional]): Default props to pass to each new item added, when using a `array` field type
472
+
473
+ ### External Fields
474
+
475
+ External fields can be used to load content from an external content repository, like Strapi.js.
476
+
477
+ - **type** (`"external"`)
478
+ - **placeholder** (`string`): A placeholder for the external field button
479
+ - **fetchList** (`() => object`): Fetch content from a third-party API and return an array
480
+ - **mapProp** (`(selectedItem: object) => object`): Map the selected item into another shape
481
+
482
+ ### Custom Fields
483
+
484
+ - **type** (`"custom"`)
294
485
  - **render** (`Component`): Render a custom field. Receives the props:
295
486
  - **field** (`Field`): Field configuration
296
487
  - **name** (`string`): Name of the field
@@ -307,27 +498,30 @@ The `AppState` object stores the puck application state.
307
498
  - **leftSideBarVisible** (boolean): Whether or not the left side bar is visible
308
499
  - **itemSelector** (object): An object describing which item is selected
309
500
  - **arrayState** (object): An object describing the internal state of array items
501
+ - **componentList** (object): An object describing the component list. Similar shape to `Config.categories`.
502
+ - **components** (`sting[]`, [optional]): Array containing the names of components in this category
503
+ - **title** (`sting`, [optional]): Title of the category
504
+ - **visible** (`boolean`, [optional]): Whether or not the category is visible in the side bar
505
+ - **expanded** (`boolean`, [optional]): Whether or not the category is expanded in the side bar
310
506
 
311
507
  ### `Data`
312
508
 
313
509
  The `Data` object stores the puck page data.
314
510
 
315
- - **root** (`object`):
316
- - **title** (string): Title of the content, typically used for the page title
317
- - **[prop]** (string): User defined data from `root` fields
318
- - **content** (`object[]`):
319
- - **type** (string): Component name
320
- - **props** (object):
321
- - **[prop]** (string): User defined data from component fields
322
-
323
- ### `Adaptor`
324
-
325
- An `Adaptor` can be used to load content from an external content repository, like Strapi.js.
511
+ - **root** (`ComponentData`): The component data for the root of your configuration.
512
+ - **props** (object): Extends `ComponentData.props`, with some additional props
513
+ - **title** (`string`, [optional]): Title of the content, typically used for the page title
514
+ - **content** (`ComponentData[]`): Component data for the main content
515
+ - **zones** (`object`, [optional]): Component data for all DropZones
516
+ **[zoneCompound]** (`ComponentData[]`): Component data for a specific DropZone `zone` within a component instance
326
517
 
327
- - **name** (`string`): The human-readable name of the adaptor
328
- - **fetchList** (`(adaptorParams: object) => object`): Fetch a list of content and return an array
518
+ ### `ComponentData`
329
519
 
330
- > NB Using an adaptor on the reserved field name `_data` will spread the resulting data over your object, and lock the overridden fields.
520
+ - **type** (`string`): Component name
521
+ - **props** (`object`):
522
+ - **[prop]** (`any`): User defined data from component fields
523
+ - **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`.
524
+ - **[prop]** (`boolean`): boolean describing whether or not the prop field is read-only
331
525
 
332
526
  ### `Plugin`
333
527