diff --git a/packages/docs/.storybook/preview.tsx b/packages/docs/.storybook/preview.tsx index b3cd2f12..be18fd44 100644 --- a/packages/docs/.storybook/preview.tsx +++ b/packages/docs/.storybook/preview.tsx @@ -28,9 +28,15 @@ const parameters: Parameters = { 'Components', [ 'Prices', - ['PricesContainer', '*'], + ['PricesContainer'], 'Skus', - ['AvailabilityContainer', '*'] + [ + 'SkusContainer', + 'Skus', + 'SkuField', + 'AvailabilityContainer', + 'AvailabilityTemplate' + ] ] ] } diff --git a/packages/docs/stories/_internals/CommerceLayer.tsx b/packages/docs/stories/_internals/CommerceLayer.tsx index 2625f90c..b9cfec3c 100644 --- a/packages/docs/stories/_internals/CommerceLayer.tsx +++ b/packages/docs/stories/_internals/CommerceLayer.tsx @@ -14,7 +14,7 @@ interface Props { */ function CommerceLayer({ children, ...props }: Props): JSX.Element { const { accessToken, endpoint } = useGetToken({ - userMode: props.accessToken === 'user-access-token' + userMode: props.accessToken === 'customer-access-token' }) return ( diff --git a/packages/docs/stories/_internals/useGetToken.ts b/packages/docs/stories/_internals/useGetToken.ts index 4eb2c4b5..d51ab0b7 100644 --- a/packages/docs/stories/_internals/useGetToken.ts +++ b/packages/docs/stories/_internals/useGetToken.ts @@ -65,7 +65,6 @@ export function useGetToken( accessToken === '' || isTokenExpired({ accessToken, compareTo: new Date() }) ) { - console.log('initToken') void initToken() } }, [accessToken]) diff --git a/packages/docs/stories/examples/order-list/OrderList.stories.tsx b/packages/docs/stories/examples/order-list/OrderList.stories.tsx index 0ff2a4f9..1cad57e8 100644 --- a/packages/docs/stories/examples/order-list/OrderList.stories.tsx +++ b/packages/docs/stories/examples/order-list/OrderList.stories.tsx @@ -43,7 +43,7 @@ export const Default: StoryFn = (args) => { ] return ( - + + +# Skus + +This guide will explain how to render a list of skus with prices, availability, and add to cart buttons. + +As first thing, it uses both `` and `` to manage the state of the cart (remember that for us, the cart is a draft order). +In this way the `` knows to which order it should add the sku. + + + + // ...rest of the app + + +`} /> + + +The presentational part of the skus is handled with `` and `` components. + + + + + +
+ // template used to render each sku +
+
+
+ +
+ +`} /> + + +Inside the `` component we load all other components that are needed to render the skus, +including the ``, the `` and the ``. + + +Please note that we don't need to loop though our `skus` array. The content will be rendered for each sku passed to our `SkusContainer`. + + + + + + + // <-- gets current sku_code from + + + + // <-- gets current sku_code from + + + + // <-- gets current sku_code from and will add new line_items to the + +`} /> + + +To complete the example, we added a final block to the page that rapresent the items added to the cart (using ``). +In this way you will be able to see a full interaction between nested contexts (OrderContainer and SkuContainer). + + +### Final example + +Here you can see the final example that applies the block explained above, ready to be used in your application. + + diff --git a/packages/docs/stories/examples/skus/Skus.stories.tsx b/packages/docs/stories/examples/skus/Skus.stories.tsx new file mode 100644 index 00000000..2f4b8633 --- /dev/null +++ b/packages/docs/stories/examples/skus/Skus.stories.tsx @@ -0,0 +1,98 @@ +import type { Meta, StoryFn } from '@storybook/react' +import CommerceLayer from '../../_internals/CommerceLayer' +import { Skus } from '#components/skus/Skus' +import { SkusContainer } from '#components/skus/SkusContainer' +import { SkuField } from '#components/skus/SkuField' +import { AvailabilityContainer } from '#components/skus/AvailabilityContainer' +import AvailabilityTemplate from '#components/skus/AvailabilityTemplate' +import Price from '#components/prices/Price' +import PricesContainer from '#components/prices/PricesContainer' +import OrderContainer from '#components/orders/OrderContainer' +import AddToCartButton from '#components/orders/AddToCartButton' +import OrderStorage from '#components/orders/OrderStorage' +import LineItemsContainer from '#components/line_items/LineItemsContainer' +import Errors from '#components/errors/Errors' +import LineItemName from '#components/line_items/LineItemName' +import LineItem from '#components/line_items/LineItem' +import LineItemQuantity from '#components/line_items/LineItemQuantity' +import LineItemRemoveLink from '#components/line_items/LineItemRemoveLink' +import LineItemsEmpty from '#components/line_items/LineItemsEmpty' + +const setup: Meta = { + title: 'Examples/Skus' +} + +export default setup + +export const Default: StoryFn = (args) => { + return ( + + + + + +
+ +
+ + + + + + + +
+ +
+ + + + +
+
+
+
+ + {/* cart */} +
+ + + +
+
+ + + {({ quantity }) => (×{quantity})} + + + +
+ + + +
+ + + + ) +} diff --git a/packages/docs/stories/prices/Price.stories.tsx b/packages/docs/stories/prices/Price.stories.tsx index c25e9a31..3044c49c 100644 --- a/packages/docs/stories/prices/Price.stories.tsx +++ b/packages/docs/stories/prices/Price.stories.tsx @@ -12,10 +12,7 @@ export default setup const Template: StoryFn = (args) => { return ( - + diff --git a/packages/docs/stories/prices/PricesContainer.stories.tsx b/packages/docs/stories/prices/PricesContainer.stories.tsx index c77a890d..9e773be1 100644 --- a/packages/docs/stories/prices/PricesContainer.stories.tsx +++ b/packages/docs/stories/prices/PricesContainer.stories.tsx @@ -12,10 +12,7 @@ export default setup const Template: StoryFn = (args) => { return ( - + diff --git a/packages/docs/stories/skus/Availability.stories.tsx b/packages/docs/stories/skus/AvailabilityTemplate.stories.tsx similarity index 92% rename from packages/docs/stories/skus/Availability.stories.tsx rename to packages/docs/stories/skus/AvailabilityTemplate.stories.tsx index ff11eeeb..439e591e 100644 --- a/packages/docs/stories/skus/Availability.stories.tsx +++ b/packages/docs/stories/skus/AvailabilityTemplate.stories.tsx @@ -5,7 +5,7 @@ import { AvailabilityContainer } from '#components/skus/AvailabilityContainer' import PricesContainer from '#components/prices/PricesContainer' const setup: Meta = { - title: 'Components/Skus/Availability', + title: 'Components/Skus/AvailabilityTemplate', component: AvailabilityTemplate, argTypes: { showShippingMethodName: { @@ -25,10 +25,7 @@ export default setup const Template: StoryFn = (args) => { return ( - + @@ -97,9 +94,7 @@ export const ChildrenProps: StoryObj = () => { {childrenProps.min?.hours} hours

The delivery_lead_times object

-
-                  {JSON.stringify(childrenProps, null, 2)}
-                
+
{JSON.stringify(childrenProps, null, 2)}
) }} diff --git a/packages/docs/stories/skus/Skus.stories.tsx b/packages/docs/stories/skus/Skus.stories.tsx new file mode 100644 index 00000000..dff99239 --- /dev/null +++ b/packages/docs/stories/skus/Skus.stories.tsx @@ -0,0 +1,28 @@ +import type { Meta, StoryFn } from '@storybook/react' +import CommerceLayer from '../_internals/CommerceLayer' +import { Skus } from '#components/skus/Skus' +import { SkusContainer } from '#components/skus/SkusContainer' +import { Code } from 'stories/_internals/Code' + +const setup: Meta = { + title: 'Components/Skus/Skus', + component: Skus +} + +export default setup + +export const Default: StoryFn = () => { + return ( + + + +
+ I am Skus child +
+
+
+
+ ) +} diff --git a/packages/docs/stories/skus/SkusContainer.stories.tsx b/packages/docs/stories/skus/SkusContainer.stories.tsx new file mode 100644 index 00000000..b74dc4f8 --- /dev/null +++ b/packages/docs/stories/skus/SkusContainer.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryFn } from '@storybook/react' +import CommerceLayer from '../_internals/CommerceLayer' +import { SkusContainer } from '#components/skus/SkusContainer' +import { Code } from '../_internals/Code' + +const setup: Meta = { + title: 'Components/Skus/SkusContainer', + component: SkusContainer +} + +export default setup + +const Template: StoryFn = (args) => { + return ( + + +
+ I am the skus container, responsible to fetch alls skus{' '} + details and make them available to the children components. +
+
+
+ ) +} + +export const Default = Template.bind({}) +Default.args = { + skus: ['POLOMXXX000000FFFFFFLXXX', 'CROPTOPWFFFFFF000000XSXX'], + queryParams: { + pageSize: 25, + pageNumber: 1, + fields: ['name', 'description', 'image_url', 'reference'], + sort: { + name: 'asc' + } + } +} diff --git a/packages/docs/stories/skus/SkusField.stories.tsx b/packages/docs/stories/skus/SkusField.stories.tsx new file mode 100644 index 00000000..90a5e48b --- /dev/null +++ b/packages/docs/stories/skus/SkusField.stories.tsx @@ -0,0 +1,98 @@ +import { type Meta, type StoryFn, type StoryObj } from '@storybook/react' +import CommerceLayer from '../_internals/CommerceLayer' +import { Skus } from '#components/skus/Skus' +import { SkusContainer } from '#components/skus/SkusContainer' +import { SkuField } from '#components/skus/SkuField' + +const setup: Meta = { + title: 'Components/Skus/SkuField', + component: SkuField, + argTypes: { + attribute: { + control: 'select', + options: ['name', 'code', 'description', 'image_url', 'weight'], + description: 'Resource attribute to display' + }, + tagElement: { + control: 'select', + options: ['div', 'p', 'span', 'img', 'section'], + description: + 'Resource attribute to displayHtml tag to render. When tag is `img` the value will be used to fill the `src` attribute.' + } + } +} + +export default setup + +const Template: StoryFn = (args) => { + return ( + + + + + + + + ) +} + +export const Default = Template.bind({}) +Default.args = { + attribute: 'name', + tagElement: 'div' +} + +/** + * The `image_url` can be displayed as `` tag by setting `tagElement` prop to `img`. + * In general you can use the `tagElement` you prefer to render the `attribute` you need. + */ +export const SkuImageAsImgTag = Template.bind({}) +SkuImageAsImgTag.args = { + attribute: 'image_url', + tagElement: 'img', + width: 100 +} + +/** + * You can access the attribute value using the children props. + * In this way you will be able to apply your own logic if the default rendering is not enough. + * + * Using the children props is a good idea when you need to render skus' `metadata` the are usually stored as a JSON object or array. + * In this way you can access `childrenProps.attributeValue` and cycle through the object/array. + * + */ +export const ChildrenProps: StoryObj = () => { + return ( + + {(childrenProps: any) => { + return
{JSON.stringify(childrenProps, null, 2)}
+ }} +
+ ) +} +ChildrenProps.decorators = [ + (Story) => { + return ( + + + + + + + + ) + } +] +ChildrenProps.parameters = { + docs: { + source: { + type: 'code' + } + } +} diff --git a/packages/react-components/src/components/skus/AvailabilityContainer.tsx b/packages/react-components/src/components/skus/AvailabilityContainer.tsx index 0e09f183..03acbb39 100644 --- a/packages/react-components/src/components/skus/AvailabilityContainer.tsx +++ b/packages/react-components/src/components/skus/AvailabilityContainer.tsx @@ -27,7 +27,8 @@ interface Props { * * It can be used to fetch the quantities for a specific `sku_code` passed as prop. * - * Must be a child of the `` component. + * Must be a child of the `` component.
+ * Can be a child of the `` component and receive the `sku_code` from its context. *
* * `` diff --git a/packages/react-components/src/components/skus/SkuField.tsx b/packages/react-components/src/components/skus/SkuField.tsx index 458690fb..5089a7ea 100644 --- a/packages/react-components/src/components/skus/SkuField.tsx +++ b/packages/react-components/src/components/skus/SkuField.tsx @@ -14,8 +14,19 @@ type Props = { children?: (props: SkuFieldChildrenProps) => JSX.Element } & TCondition /** - * @param props {@link Props} - * @returns + * The SkuField component displays any attribute of the `sku` specified in the parent `` component. + * + * It also accepts a `tagElement` props to enable specific tag-related props. + * For examples, when `tagElement` is set to `img` it will also accept props related to `` tag such as `height` and `width`. + * + * + * It must to be used inside the `` component. + * + + * + * Check the `skus` resource from our [Core API documentation](https://docs.commercelayer.io/core/v/api-reference/skus/object) + * for more details about the available attributes to render. + * */ export function SkuField

(props: P): JSX.Element { const { attribute, tagElement = 'span', children, ...p } = props diff --git a/packages/react-components/src/components/skus/Skus.tsx b/packages/react-components/src/components/skus/Skus.tsx index dbfa5e90..02d9b698 100644 --- a/packages/react-components/src/components/skus/Skus.tsx +++ b/packages/react-components/src/components/skus/Skus.tsx @@ -6,6 +6,18 @@ interface Props { children: ReactNode } +/** + * The `Skus` components loop through the list of `skus` found in `SkusContainer` context and render the children filled with the proper sku value. + * This means that the children will be rendered as many times as the number of skus found in the `skus` prop array + * and there is no need to manually loop through them from your consumer app. + * + * + * Must be a child of the `` component. + * + * + * ``, `` + * + */ export function Skus

({ children }: P): JSX.Element { const { skus } = useContext(SkuContext) const components = skus?.map((sku, key) => { diff --git a/packages/react-components/src/components/skus/SkusContainer.tsx b/packages/react-components/src/components/skus/SkusContainer.tsx index 49398795..f14682e8 100644 --- a/packages/react-components/src/components/skus/SkusContainer.tsx +++ b/packages/react-components/src/components/skus/SkusContainer.tsx @@ -16,7 +16,7 @@ interface Props { */ skus: string[] /** - * Accept a React node, [Skus](./Skus.d.ts), and [ItemContainer](../ItemContainer.d.ts) as children to display above the skus. + * Accept a React node and [Skus](./Skus.d.ts) component as children to display above the skus. */ children: ReactNode /** @@ -25,6 +25,19 @@ interface Props { queryParams?: QueryParamsList } +/** + * Main container for the SKUs components. + * It stores - in its context - the details for each `sku` defined in the `skus` prop array. + * + * It also accept a `queryParams` prop to refine pagination, sorting, filtering and includes for the fetch request. + * + * + * Must be a child of the `` component. + * + * + * `` + * + */ export function SkusContainer

(props: P): JSX.Element { const { skus, children, queryParams } = props const [state, dispatch] = useReducer(skuReducer, skuInitialState) diff --git a/packages/react-components/src/components/utils/GenericFieldComponent.tsx b/packages/react-components/src/components/utils/GenericFieldComponent.tsx index 42fa2f82..1e38d0df 100644 --- a/packages/react-components/src/components/utils/GenericFieldComponent.tsx +++ b/packages/react-components/src/components/utils/GenericFieldComponent.tsx @@ -54,7 +54,13 @@ type GenericContext = Context< interface Props { children?: (props: TGenericChildrenProps) => JSX.Element resource: E['resource'] + /** + * Resource attribute to display. + */ attribute: keyof E + /** + * Html tag to render. When tag is `img` the value will be used to fill the `src` attribute. + */ tagElement: keyof JSX.IntrinsicElements context: GenericContext }