@measured/puck 0.11.0-canary.6145c32 → 0.11.0-canary.b28404f

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,163 @@ 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
+ ## Adaptors
238
+
239
+ Adaptors 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 use an adaptor to query data from a third party API:
244
+
245
+ ```tsx
246
+ const myAdaptor = {
247
+ name: "My adaptor",
248
+ fetchList: async () => {
249
+ const response = await fetch("https://www.example.com/api");
250
+
251
+ return {
252
+ text: response.json().text,
253
+ };
254
+ },
255
+ };
256
+
257
+ const config = {
258
+ components: {
259
+ HeadingBlock: {
260
+ fields: {
261
+ myData: {
262
+ type: "external",
263
+ adaptor: myAdaptor,
264
+ },
265
+ },
266
+ render: ({ myData }) => {
267
+ return <h1>{myData.text}</h1>;
268
+ },
269
+ },
270
+ },
271
+ };
272
+ ```
273
+
274
+ When the user interacts with this adaptor, 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`.
275
+
276
+ ## Dynamic prop resolution
277
+
278
+ Dynamic prop resolution allows developers to resolve props for components without saving the data to the Puck data model.
279
+
280
+ ### resolveProps()
281
+
282
+ `resolveProps` is defined in the component config, and allows the developer to make asynchronous calls to change the props after they've been set by Puck.
283
+
284
+ #### Args
285
+
286
+ - **props** (`object`): the current props for your component stored in the Puck data
287
+
288
+ #### Response
289
+
290
+ - **props** (`object`): the resolved props for your component. Will not be stored in the Puck data
291
+ - **readOnly** (`object`): an object describing which fields on the component are currently read-only
292
+ - **[prop]** (`boolean`): boolean describing whether or not the prop field is read-only
293
+
294
+ #### Examples
295
+
296
+ ##### Basic example
297
+
298
+ In this example, we remap the `text` prop to the `title` prop and mark the `title` field as read-only.
299
+
300
+ ```tsx
301
+ const config = {
302
+ components: {
303
+ HeadingBlock: {
304
+ fields: {
305
+ text: {
306
+ type: "text",
307
+ },
308
+ title: {
309
+ type: "text",
310
+ },
311
+ },
312
+ resolveProps: async (props) => {
313
+ return {
314
+ props: {
315
+ title: props.text,
316
+ },
317
+ readOnly: {
318
+ title: true,
319
+ },
320
+ };
321
+ },
322
+ render: ({ title }) => {
323
+ return <h1>{title}</h1>;
324
+ },
325
+ },
326
+ },
327
+ };
328
+ ```
329
+
330
+ ##### Combining with adaptors
331
+
332
+ A more advanced pattern is to combine the `resolveProps` method with the adaptors to dynamically fetch data when rendering the component.
333
+
334
+ ```tsx
335
+ const myAdaptor = {
336
+ name: "My adaptor",
337
+ fetchList: async () => {
338
+ const response = await fetch("https://www.example.com/api");
339
+
340
+ return {
341
+ id: response.json().id,
342
+ };
343
+ },
344
+ };
345
+
346
+ const config = {
347
+ components: {
348
+ HeadingBlock: {
349
+ fields: {
350
+ myData: {
351
+ type: "external",
352
+ adaptor: myAdaptor,
353
+ },
354
+ title: {
355
+ type: "text",
356
+ },
357
+ },
358
+ resolveProps: async (props) => {
359
+ if (!myData.id) {
360
+ return { props, readOnly: { title: false } };
361
+ }
362
+
363
+ const latestData = await fetch(
364
+ `https://www.example.com/api/${myData.id}`
365
+ );
366
+
367
+ return {
368
+ props: {
369
+ title: latestData.json().text,
370
+ },
371
+ readOnly: {
372
+ title: true,
373
+ },
374
+ };
375
+ },
376
+ render: ({ title }) => {
377
+ return <h1>{title}</h1>;
378
+ },
379
+ },
380
+ },
381
+ };
382
+ ```
383
+
384
+ ### resolveData()
385
+
386
+ `resolveData` is a utility function exported by Puck to enable the developer to resolve their custom props before rendering their component with `<Render>`. This is ideally done on the server. If you're using `resolveProps`, you _must_ use `resolveData` before rendering.
387
+
388
+ ```tsx
389
+ import { resolveData } from "@measured/puck";
390
+
391
+ const resolvedData = resolveData(data, config);
392
+ ```
393
+
234
394
  ## Reference
235
395
 
236
396
  ### `<Puck>`
@@ -241,6 +401,7 @@ The `<Puck>` component renders the Puck editor.
241
401
  - **data** (`Data`): Initial data to render
242
402
  - **onChange** (`(Data) => void` [optional]): Callback that triggers when the user makes a change
243
403
  - **onPublish** (`(Data) => void` [optional]): Callback that triggers when the user hits the "Publish" button
404
+ - **renderComponentList** (`Component` [optional]): Render function for wrapping the component list
244
405
  - **renderHeader** (`Component` [optional]): Render function for overriding the Puck header component
245
406
  - **renderHeaderActions** (`Component` [optional]): Render function for overriding the Puck header actions. Use a fragment.
246
407
  - **headerTitle** (`string` [optional]): Set the title shown in the header title
@@ -275,22 +436,76 @@ The `Config` object describes which components Puck should render, how they shou
275
436
  - **fields** (`Field`): The Field objects describing the input data stored against this component.
276
437
  - **render** (`Component`): Render function for your React component. Receives props as defined in fields.
277
438
  - **defaultProps** (`object` [optional]): Default props to pass to your component. Will show in fields.
439
+ - **resolveProps** (`async (props: object) => object` [optional]): Function to dynamically change props before rendering the component.
440
+ - Args
441
+ - **props** (`object`): the current props for your component stored in the Puck data
442
+ - Response
443
+ - **props** (`object`): the resolved props for your component. Will not be stored in the Puck data
444
+ - **readOnly** (`object`): an object describing which fields on the component are currently read-only
445
+ - **[prop]** (`boolean`): boolean describing whether or not the prop field is read-only
446
+ - **categories** (`object`): Component categories for rendering in the side bar or restricting in DropZones
447
+ - **[categoryName]** (`object`)
448
+ - **components** (`sting[]`, [optional]): Array containing the names of components in this category
449
+ - **title** (`sting`, [optional]): Title of the category
450
+ - **visible** (`boolean`, [optional]): Whether or not the category should be visible in the side bar
451
+ - **defaultExpanded** (`boolean`, [optional]): Whether or not the category should be expanded in the side bar by default
278
452
 
279
453
  ### `Field`
280
454
 
281
455
  A `Field` represents a user input field shown in the Puck interface.
282
456
 
283
- - **type** (`text` | `textarea` | `number` | `select` | `radio` | `external` | `array` | `custom`): The input type to render
457
+ ### All Fields
458
+
284
459
  - **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
460
+
461
+ ### Text Fields
462
+
463
+ - **type** (`"text"`)
464
+
465
+ ### Textarea Fields
466
+
467
+ - **type** (`"textarea"`)
468
+
469
+ ### Number Fields
470
+
471
+ - **type** (`"number"`)
472
+
473
+ ### Select Fields
474
+
475
+ - **type** (`"select"`)
476
+ - **options** (`object[]`): array of items to render
477
+ - **label** (`string`)
478
+ - **value** (`string` | `number` | `boolean`)
479
+
480
+ ### Radio Fields
481
+
482
+ - **type** (`"radio"`)
483
+ - **options** (`object[]`): array of items to render
290
484
  - **label** (`string`)
291
485
  - **value** (`string` | `number` | `boolean`)
292
- - **adaptor** (`Adaptor`): Content adaptor if using the `external` input type
486
+
487
+ ### Array Fields
488
+
489
+ - **type** (`"array"`)
490
+ - **arrayFields** (`object`): Object describing sub-fields for each item
491
+ - **[fieldName]** (`Field`): The Field objects describing the input data for each item
492
+ - **getItemSummary** (`(object, number) => string` [optional]): Function to get the label of each item
493
+ - **defaultItemProps** (`object` [optional]): Default props to pass to each new item added, when using a `array` field type
494
+
495
+ ### External Fields
496
+
497
+ External fields can be used to load content from an external content repository, like Strapi.js, using an `Adaptor`.
498
+
499
+ - **type** (`"external"`)
500
+ - **adaptor** (`Adaptor`): Content adaptor responsible for fetching data to show in the table
501
+ - **name** (`string`): The human-readable name of the adaptor
502
+ - **fetchList** (`(adaptorParams: object) => object`): Fetch content from a third-party API and return an array
503
+ - **mapProp** (`(selectedItem: object) => object`): Map the selected item into another shape
293
504
  - **adaptorParams** (`object`): Paramaters passed to the adaptor
505
+
506
+ ### Custom Fields
507
+
508
+ - **type** (`"custom"`)
294
509
  - **render** (`Component`): Render a custom field. Receives the props:
295
510
  - **field** (`Field`): Field configuration
296
511
  - **name** (`string`): Name of the field
@@ -307,6 +522,11 @@ The `AppState` object stores the puck application state.
307
522
  - **leftSideBarVisible** (boolean): Whether or not the left side bar is visible
308
523
  - **itemSelector** (object): An object describing which item is selected
309
524
  - **arrayState** (object): An object describing the internal state of array items
525
+ - **componentList** (object): An object describing the component list. Similar shape to `Config.categories`.
526
+ - **components** (`sting[]`, [optional]): Array containing the names of components in this category
527
+ - **title** (`sting`, [optional]): Title of the category
528
+ - **visible** (`boolean`, [optional]): Whether or not the category is visible in the side bar
529
+ - **expanded** (`boolean`, [optional]): Whether or not the category is expanded in the side bar
310
530
 
311
531
  ### `Data`
312
532
 
@@ -320,15 +540,6 @@ The `Data` object stores the puck page data.
320
540
  - **props** (object):
321
541
  - **[prop]** (string): User defined data from component fields
322
542
 
323
- ### `Adaptor`
324
-
325
- An `Adaptor` can be used to load content from an external content repository, like Strapi.js.
326
-
327
- - **name** (`string`): The human-readable name of the adaptor
328
- - **fetchList** (`(adaptorParams: object) => object`): Fetch a list of content and return an array
329
-
330
- > NB Using an adaptor on the reserved field name `_data` will spread the resulting data over your object, and lock the overridden fields.
331
-
332
543
  ### `Plugin`
333
544
 
334
545
  Plugins that can be used to enhance Puck.
package/dist/index.css CHANGED
@@ -475,17 +475,17 @@
475
475
  }
476
476
 
477
477
  /* css-module:/home/runner/work/puck/puck/packages/core/components/InputOrGroup/fields/ArrayField/styles.module.css/#css-module-data */
478
- ._ArrayField_zp334_5 {
478
+ ._ArrayField_32vor_5 {
479
479
  display: flex;
480
480
  flex-direction: column;
481
481
  background-color: var(--puck-color-grey-8);
482
482
  border: 1px solid var(--puck-color-grey-8);
483
483
  border-radius: 4px;
484
484
  }
485
- ._ArrayField--isDraggingFrom_zp334_13 {
485
+ ._ArrayField--isDraggingFrom_32vor_13 {
486
486
  background-color: var(--puck-color-azure-9);
487
487
  }
488
- ._ArrayField-addButton_zp334_17 {
488
+ ._ArrayField-addButton_32vor_17 {
489
489
  background-color: white;
490
490
  border: none;
491
491
  border-radius: 4px;
@@ -498,37 +498,37 @@
498
498
  padding: 16px;
499
499
  text-align: left;
500
500
  }
501
- ._ArrayField--hasItems_zp334_31 > ._ArrayField-addButton_zp334_17 {
501
+ ._ArrayField--hasItems_32vor_31 > ._ArrayField-addButton_32vor_17 {
502
502
  border-top-left-radius: 0;
503
503
  border-top-right-radius: 0;
504
504
  }
505
- ._ArrayField_zp334_5:not(._ArrayField--isDraggingFrom_zp334_13) > ._ArrayField-addButton_zp334_17:hover {
505
+ ._ArrayField_32vor_5:not(._ArrayField--isDraggingFrom_32vor_13) > ._ArrayField-addButton_32vor_17:hover {
506
506
  background: var(--puck-color-azure-9);
507
507
  color: var(--puck-color-azure-4);
508
508
  }
509
- ._ArrayFieldItem_zp334_45 {
509
+ ._ArrayFieldItem_32vor_45 {
510
510
  background: white;
511
511
  border-top-left-radius: 4px;
512
512
  border-top-right-radius: 4px;
513
513
  display: block;
514
514
  margin-bottom: 1px;
515
515
  }
516
- ._ArrayField--isDraggingFrom_zp334_13 > ._ArrayFieldItem_zp334_45:not(._ArrayFieldItem--isDragging_zp334_53) {
516
+ ._ArrayField--isDraggingFrom_32vor_13 > ._ArrayFieldItem_32vor_45:not(._ArrayFieldItem--isDragging_32vor_53) {
517
517
  border-bottom: 1px solid var(--puck-color-grey-8);
518
518
  margin-bottom: 0;
519
519
  }
520
- ._ArrayFieldItem--isExpanded_zp334_58 {
520
+ ._ArrayFieldItem--isExpanded_32vor_58 {
521
521
  border-bottom: 0;
522
522
  outline: 1px solid var(--puck-color-azure-6);
523
523
  }
524
- ._ArrayFieldItem--isDragging_zp334_53 {
524
+ ._ArrayFieldItem--isDragging_32vor_53 {
525
525
  box-shadow: rgba(140, 152, 164, 0.25) 0 3px 6px 0;
526
526
  }
527
- ._ArrayFieldItem_zp334_45 + ._ArrayFieldItem_zp334_45 {
527
+ ._ArrayFieldItem_32vor_45 + ._ArrayFieldItem_32vor_45 {
528
528
  border-top-left-radius: 0;
529
529
  border-top-right-radius: 0;
530
530
  }
531
- ._ArrayFieldItem-summary_zp334_72 {
531
+ ._ArrayFieldItem-summary_32vor_72 {
532
532
  color: var(--puck-color-grey-3);
533
533
  display: flex;
534
534
  align-items: center;
@@ -539,48 +539,45 @@
539
539
  position: relative;
540
540
  overflow: hidden;
541
541
  }
542
- ._ArrayFieldItem--isExpanded_zp334_58 > ._ArrayFieldItem-summary_zp334_72 {
542
+ ._ArrayFieldItem--isExpanded_32vor_58 > ._ArrayFieldItem-summary_32vor_72 {
543
543
  font-weight: 600;
544
544
  }
545
- ._ArrayFieldItem_zp334_45:first-of-type > ._ArrayFieldItem-summary_zp334_72 {
545
+ ._ArrayFieldItem_32vor_45:first-of-type > ._ArrayFieldItem-summary_32vor_72 {
546
546
  border-top-left-radius: 4px;
547
547
  border-top-right-radius: 4px;
548
548
  }
549
- ._ArrayFieldItem-summary_zp334_72:hover,
550
- ._ArrayFieldItem--isExpanded_zp334_58 > ._ArrayFieldItem-summary_zp334_72 {
549
+ ._ArrayFieldItem-summary_32vor_72:hover,
550
+ ._ArrayFieldItem--isExpanded_32vor_58 > ._ArrayFieldItem-summary_32vor_72 {
551
551
  background: var(--puck-color-azure-9);
552
552
  cursor: pointer;
553
553
  color: var(--puck-color-azure-4);
554
554
  }
555
- ._ArrayFieldItem-summary_zp334_72::-webkit-details-marker {
555
+ ._ArrayFieldItem-body_32vor_100 {
556
556
  display: none;
557
557
  }
558
- ._ArrayFieldItem-body_zp334_104 {
559
- display: none;
560
- }
561
- ._ArrayFieldItem--isExpanded_zp334_58 > ._ArrayFieldItem-body_zp334_104 {
558
+ ._ArrayFieldItem--isExpanded_32vor_58 > ._ArrayFieldItem-body_32vor_100 {
562
559
  display: block;
563
560
  }
564
- ._ArrayFieldItem-fieldset_zp334_112 {
561
+ ._ArrayFieldItem-fieldset_32vor_108 {
565
562
  border: none;
566
563
  border-top: 1px solid var(--puck-color-grey-8);
567
564
  margin: 0;
568
565
  padding: 16px;
569
566
  }
570
- ._ArrayFieldItem-rhs_zp334_119 {
567
+ ._ArrayFieldItem-rhs_32vor_115 {
571
568
  display: flex;
572
569
  gap: 8px;
573
570
  align-items: center;
574
571
  }
575
- ._ArrayFieldItem-actions_zp334_125 {
572
+ ._ArrayFieldItem-actions_32vor_121 {
576
573
  display: flex;
577
574
  gap: 8px;
578
575
  opacity: 0;
579
576
  }
580
- ._ArrayFieldItem-summary_zp334_72:hover > ._ArrayFieldItem-rhs_zp334_119 > ._ArrayFieldItem-actions_zp334_125 {
577
+ ._ArrayFieldItem-summary_32vor_72:hover > ._ArrayFieldItem-rhs_32vor_115 > ._ArrayFieldItem-actions_32vor_121 {
581
578
  opacity: 1;
582
579
  }
583
- ._ArrayFieldItem-action_zp334_125:hover {
580
+ ._ArrayFieldItem-action_32vor_121:hover {
584
581
  background: var(--puck-color-grey-9);
585
582
  border-radius: 4px;
586
583
  color: var(--puck-color-blue);
@@ -723,11 +720,44 @@
723
720
  }
724
721
 
725
722
  /* css-module:/home/runner/work/puck/puck/packages/core/components/ComponentList/styles.module.css/#css-module-data */
726
- ._ComponentList_bvy0z_1 {
723
+ ._ComponentList_3rdy2_1 {
727
724
  font-family: var(--puck-font-stack);
728
725
  max-width: 100%;
729
726
  }
730
- ._ComponentList-item_bvy0z_6 {
727
+ ._ComponentList--isExpanded_3rdy2_6 + ._ComponentList_3rdy2_1 {
728
+ margin-top: 12px;
729
+ }
730
+ ._ComponentList-content_3rdy2_10 {
731
+ display: none;
732
+ }
733
+ ._ComponentList--isExpanded_3rdy2_6 > ._ComponentList-content_3rdy2_10 {
734
+ display: block;
735
+ }
736
+ ._ComponentList-title_3rdy2_18 {
737
+ color: var(--puck-color-grey-4);
738
+ display: flex;
739
+ font-size: var(--puck-font-size-xxxs);
740
+ list-style: none;
741
+ padding: 8px;
742
+ text-transform: uppercase;
743
+ gap: 4px;
744
+ border-radius: 4px;
745
+ }
746
+ ._ComponentList--isExpanded_3rdy2_6 ._ComponentList-title_3rdy2_18 {
747
+ margin-bottom: 4px;
748
+ }
749
+ ._ComponentList-title_3rdy2_18:hover {
750
+ background-color: var(--puck-color-azure-9);
751
+ color: var(--puck-color-azure-4);
752
+ cursor: pointer;
753
+ }
754
+ ._ComponentList-titleIcon_3rdy2_39 {
755
+ margin-left: auto;
756
+ }
757
+ ._ComponentListItem_3rdy2_43:last-of-type ._ComponentListItem-draggable_3rdy2_43 {
758
+ margin-bottom: 0px;
759
+ }
760
+ ._ComponentListItem-draggable_3rdy2_43 {
731
761
  background: white;
732
762
  padding: 12px;
733
763
  display: flex;
@@ -740,13 +770,10 @@
740
770
  cursor: grab;
741
771
  margin-bottom: 12px;
742
772
  }
743
- ._ComponentList-item_bvy0z_6:last-of-type {
744
- margin-bottom: 0px;
745
- }
746
- ._ComponentList-itemIcon_bvy0z_24 {
773
+ ._ComponentListItemIcon_3rdy2_61 {
747
774
  color: var(--puck-color-grey-4);
748
775
  }
749
- ._ComponentList_bvy0z_1:not(._ComponentList--isDraggingFrom_bvy0z_28) ._ComponentList-item_bvy0z_6:hover {
776
+ ._ComponentList_3rdy2_1:not(._ComponentList--isDraggingFrom_3rdy2_65) ._ComponentListItem-draggable_3rdy2_43:hover {
750
777
  background-color: var(--puck-color-azure-9);
751
778
  color: var(--puck-color-azure-4);
752
779
  }