diff --git a/README.md b/README.md index 0682bf0d1c..a932ca0d9d 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ A `Field` represents a user input field shown in the Puck interface. - **label** (`text` [optional]): A label for the input. Will use the key if not provided. - **arrayFields** (`object`): Object describing sub-fields for items in an `array` input - **[fieldName]** (`Field`): The Field objects describing the input data for each item -- **getItemSummary** (`(object, number) => string` [optional]): Function to get the name of each item when using an `array` field type +- **getItemSummary** (`(object, number) => string` [optional]): Function to get the name of each item when using the `array` or `external` field types - **defaultItemProps** (`object` [optional]): Default props to pass to each new item added, when using a `array` field type - **options** (`object[]`): array of items to render for select-type inputs - **label** (`string`) diff --git a/apps/demo/app/configs/custom/blocks/Hero/index.tsx b/apps/demo/app/configs/custom/blocks/Hero/index.tsx index 48f68021fe..c60dcc8393 100644 --- a/apps/demo/app/configs/custom/blocks/Hero/index.tsx +++ b/apps/demo/app/configs/custom/blocks/Hero/index.tsx @@ -5,10 +5,12 @@ import styles from "./styles.module.css"; import { getClassNameFactory } from "@measured/puck/lib"; import { Button } from "@measured/puck/components/Button"; import { Section } from "../../components/Section"; +import createAdaptor from "@measured/puck-adaptor-fetch/index"; const getClassName = getClassNameFactory("Hero", styles); export type HeroProps = { + _data?: object; title: string; description: string; align?: string; @@ -18,8 +20,30 @@ export type HeroProps = { buttons: { label: string; href: string; variant?: "primary" | "secondary" }[]; }; +const quotesAdaptor = createAdaptor( + "Quotes API", + "https://api.quotable.io/quotes", + (body) => + body.results.map((item) => ({ + ...item, + title: item.author, + description: item.content, + })) +); + export const Hero: ComponentConfig = { fields: { + _data: { + type: "external", + adaptor: quotesAdaptor, + adaptorParams: { + resource: "movies", + url: "http://localhost:1337", + apiToken: + "1fb8c347243145a8824481bd1008b95367677654ebc1f06c5a0a766e57b8859bcfde77f9b21405011f20eb8a13abb7b0ea3ba2393167ffc4a2cdc3828586494cc9983d5f1db4bc195ff4afb885aa55ee28a88ca796e7b883b9e9b80c98b50eadf79f5a1639ce8e2ae4cf63c2e8b8659a8cbbfeaa8e8adcce222b827eec49f989", + }, + getItemSummary: (item: any) => item.content, + }, title: { type: "text" }, description: { type: "textarea" }, buttons: { diff --git a/packages/adaptor-fetch/README.md b/packages/adaptor-fetch/README.md index db70e1a223..33fe22c1e8 100644 --- a/packages/adaptor-fetch/README.md +++ b/packages/adaptor-fetch/README.md @@ -15,11 +15,18 @@ Create an adaptor using the [fetch() API](https://developer.mozilla.org/en-US/do ```jsx import createAdaptor from "@measured/puck-adaptor-fetch"; -const movieAdaptor = createAdaptor("http://localhost:1337/api/movies", { - headers: { - Authorization: "Bearer abc123", +const movieAdaptor = createAdaptor( + "Movies API", + "http://localhost:1337/api/movies", + // Optional request data + { + headers: { + Authorization: "Bearer abc123", + }, }, -}); + // Optional mapping function + (body) => body.data +); ``` Configure your Puck instance. In this case, we add our `movieAdaptor` to the `movie` field on our "MovieBlock" component: diff --git a/packages/adaptor-fetch/index.ts b/packages/adaptor-fetch/index.ts index 347492e96c..c8aa4b1244 100644 --- a/packages/adaptor-fetch/index.ts +++ b/packages/adaptor-fetch/index.ts @@ -1,15 +1,40 @@ -export const createAdaptor = ( +import { Adaptor } from "@measured/puck"; + +function createAdaptor( + name: string, + input: RequestInfo | URL, + map?: (data: any) => Record[] +): Adaptor; + +function createAdaptor( + name: string, input: RequestInfo | URL, init?: RequestInit, - name: string = "API" -) => ({ - name, - fetchList: async () => { - const res = await fetch(input, init); - const body: { data: Record[] } = await res.json(); - - return body.data; - }, -}); + map?: (data: any) => Record[] +): Adaptor; + +function createAdaptor( + name: string, + input: RequestInfo | URL, + initOrMap?: unknown, + map?: (data: any) => Record[] +): Adaptor { + return { + name, + fetchList: async () => { + if (typeof initOrMap === "function") { + const res = await fetch(input); + const body = await res.json(); + + return initOrMap(body); + } + + const res = await fetch(input, initOrMap as RequestInit); + const body = await res.json(); + + return map ? map(body) : body; + }, + }; +} export default createAdaptor; diff --git a/packages/adaptor-strapi/index.ts b/packages/adaptor-strapi/index.ts index 76bf621fc7..9c7abda707 100644 --- a/packages/adaptor-strapi/index.ts +++ b/packages/adaptor-strapi/index.ts @@ -1,27 +1,19 @@ -import { Adaptor } from "@measured/puck/types/Config"; +import createAdaptor from "@measured/puck-adaptor-fetch/index"; -type AdaptorParams = { apiToken: string; url: string; resource: string }; +const createStrapiAdaptor = (url: string, apiToken: string) => + createAdaptor( + "Strapi.js", + url, + { + headers: { + Authorization: `bearer ${apiToken}`, + }, + }, + (body) => + body.data.map(({ attributes, ...item }) => ({ + ...item, + ...attributes, + })) + ); -const strapiAdaptor: Adaptor = { - name: "Strapi.js", - fetchList: async (adaptorParams) => { - if (!adaptorParams) { - return null; - } - - const res = await fetch( - `${adaptorParams.url}/api/${adaptorParams.resource}`, - { - headers: { - Authorization: `bearer ${adaptorParams.apiToken}`, - }, - } - ); - - const body: { data: Record[] } = await res.json(); - - return body.data; - }, -}; - -export default strapiAdaptor; +export default createStrapiAdaptor; diff --git a/packages/core/components/ExternalInput/index.tsx b/packages/core/components/ExternalInput/index.tsx index c46ad352c4..31b4f0db7c 100644 --- a/packages/core/components/ExternalInput/index.tsx +++ b/packages/core/components/ExternalInput/index.tsx @@ -48,7 +48,9 @@ export const ExternalInput = ({ > {/* NB this is hardcoded to strapi for now */} {selectedData - ? selectedData.attributes.title + ? field.getItemSummary + ? field.getItemSummary(selectedData) + : `${field.adaptor.name} item` : `Select from ${field.adaptor.name}`} {selectedData && ( @@ -75,7 +77,7 @@ export const ExternalInput = ({ - {Object.keys(data[0].attributes).map((key) => ( + {Object.keys(data[0]).map((key) => ( @@ -83,10 +85,10 @@ export const ExternalInput = ({ - {data.map((item) => { + {data.map((item, i) => { return ( { onChange(item); @@ -96,8 +98,8 @@ export const ExternalInput = ({ setSelectedData(item); }} > - {Object.keys(item.attributes).map((key) => ( - + {Object.keys(item).map((key) => ( + ))} ); @@ -106,7 +108,7 @@ export const ExternalInput = ({
{key}
{item.attributes[key]}{item[key]}
) : ( -
No content
+
No content
)} diff --git a/packages/core/components/Puck/index.tsx b/packages/core/components/Puck/index.tsx index f1e17a1d0c..70a4789039 100644 --- a/packages/core/components/Puck/index.tsx +++ b/packages/core/components/Puck/index.tsx @@ -428,7 +428,7 @@ export function Puck({ } else { const changedFields = filter( // filter out anything not supported by this component - (value as any).attributes, // TODO type properly after getting proper state library + value, Object.keys(fields) ); diff --git a/packages/core/types/Config.tsx b/packages/core/types/Config.tsx index bc43bdd29a..79746bca9c 100644 --- a/packages/core/types/Config.tsx +++ b/packages/core/types/Config.tsx @@ -25,7 +25,7 @@ export type Field< arrayFields?: { [SubPropName in keyof Props]: Field; }; - getItemSummary?: (item: Props, index: number) => string; + getItemSummary?: (item: Props, index?: number) => string; defaultItemProps?: Props; options?: { label: string;