From d186f97856713be2b31164dfabb4745002865bb1 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Tue, 28 Apr 2026 11:36:00 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20feat(availability):=20standalon?= =?UTF-8?q?e=20Availability=20component,=20deprecate=20AvailabilityContain?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add standalone component (same API + loader prop) - Deprecate with @deprecated JSDoc - Export Availability from @commercelayer/react-components - Add StandaloneAvailability story Closes #760 --- .../availability/availability.stories.tsx | 77 +++++++++------- .../src/components/skus/Availability.tsx | 89 +++++++++++++++++++ .../components/skus/AvailabilityContainer.tsx | 4 + packages/react-components/src/index.ts | 1 + 4 files changed, 141 insertions(+), 30 deletions(-) create mode 100644 packages/react-components/src/components/skus/Availability.tsx diff --git a/packages/document/src/stories/availability/availability.stories.tsx b/packages/document/src/stories/availability/availability.stories.tsx index 1681f3c4..9df37ec3 100644 --- a/packages/document/src/stories/availability/availability.stories.tsx +++ b/packages/document/src/stories/availability/availability.stories.tsx @@ -1,25 +1,39 @@ -import type { Meta, StoryObj } from '@storybook/react-vite' -import CommerceLayer from '../_internals/CommerceLayer' import { + Availability, AvailabilityContainer, AvailabilityTemplate, - SkusContainer, - Skus, SkuField, -} from '@commercelayer/react-components' + Skus, + SkusContainer, +} from "@commercelayer/react-components" +import type { Meta, StoryObj } from "@storybook/react-vite" +import CommerceLayer from "../_internals/CommerceLayer" const meta = { - title: 'Availability/Stories', + title: "Availability/Stories", parameters: { - layout: 'centered', + layout: "centered", }, } satisfies Meta export default meta type Story = StoryObj +export const StandaloneAvailability: Story = { + name: "Availability — standalone (no container)", + render: () => ( + + + + + + ), +} + export const BasicAvailability: Story = { - name: 'AvailabilityContainer — basic', + name: "AvailabilityContainer — basic", render: () => ( @@ -30,15 +44,15 @@ export const BasicAvailability: Story = { } export const CustomLabels: Story = { - name: 'AvailabilityTemplate — custom labels', + name: "AvailabilityTemplate — custom labels", render: () => ( @@ -47,12 +61,12 @@ export const CustomLabels: Story = { } export const WithDeliveryLeadTimeDays: Story = { - name: 'AvailabilityTemplate — lead time in days', + name: "AvailabilityTemplate — lead time in days", render: () => ( @@ -61,12 +75,12 @@ export const WithDeliveryLeadTimeDays: Story = { } export const WithDeliveryLeadTimeHours: Story = { - name: 'AvailabilityTemplate — lead time in hours', + name: "AvailabilityTemplate — lead time in hours", render: () => ( @@ -75,21 +89,18 @@ export const WithDeliveryLeadTimeHours: Story = { } export const WithShippingMethodName: Story = { - name: 'AvailabilityTemplate — with shipping method name', + name: "AvailabilityTemplate — with shipping method name", render: () => ( - + ), } export const WithShippingMethodPrice: Story = { - name: 'AvailabilityTemplate — with shipping method price', + name: "AvailabilityTemplate — with shipping method price", render: () => ( @@ -104,13 +115,13 @@ export const WithShippingMethodPrice: Story = { } export const WithGetQuantityCallback: Story = { - name: 'AvailabilityContainer — getQuantity callback', + name: "AvailabilityContainer — getQuantity callback", render: () => ( { - console.log('quantity updated:', quantity) + console.log("quantity updated:", quantity) }} > @@ -120,16 +131,16 @@ export const WithGetQuantityCallback: Story = { } export const WithChildrenRenderProp: Story = { - name: 'AvailabilityTemplate — children render prop', + name: "AvailabilityTemplate — children render prop", render: () => ( {({ quantity, text, min, max }) => ( -
+
{text} {quantity > 0 && min != null && ( -

+

Ships in {min.days}–{max?.days ?? min.days} day(s)

)} @@ -142,13 +153,19 @@ export const WithChildrenRenderProp: Story = { } export const InsideSkusContainer: Story = { - name: 'AvailabilityContainer — inside SkusContainer', + name: "AvailabilityContainer — inside SkusContainer", render: () => ( - +
- + diff --git a/packages/react-components/src/components/skus/Availability.tsx b/packages/react-components/src/components/skus/Availability.tsx new file mode 100644 index 00000000..2ca0e093 --- /dev/null +++ b/packages/react-components/src/components/skus/Availability.tsx @@ -0,0 +1,89 @@ +import { useAvailability } from "@commercelayer/hooks" +import { type ReactNode, useContext, useEffect, useMemo } from "react" +import AvailabilityContext from "#context/AvailabilityContext" +import CommerceLayerContext from "#context/CommerceLayerContext" +import LineItemChildrenContext from "#context/LineItemChildrenContext" +import SkuChildrenContext from "#context/SkuChildrenContext" +import type { LoaderType } from "#typings/index" + +export interface AvailabilityProps { + children: ReactNode + /** The SKU code to fetch availability for. */ + skuCode?: string + /** + * The SKU id. Takes precedence over `skuCode` and improves performance + * by skipping the SKU lookup by code. + */ + skuId?: string + /** Callback called when the quantity is updated. */ + getQuantity?: (quantity: number) => void + /** Loader shown while fetching availability data. */ + loader?: LoaderType +} + +/** + * Displays availability data for a SKU. + * + * Can be used standalone — automatically fetches availability without an + * `AvailabilityContainer` parent. Also picks up `skuCode` from context when + * nested inside `` or a line item component. + * + * @example Standalone (preferred): + * ```tsx + * + * + * + * + * + * ``` + */ +export function Availability({ + children, + skuCode, + skuId, + getQuantity, + loader: propLoader = "Loading...", +}: AvailabilityProps): ReactNode { + const { lineItem } = useContext(LineItemChildrenContext) + const { sku } = useContext(SkuChildrenContext) + const { accessToken, interceptors } = useContext(CommerceLayerContext) + const { availability, fetchAvailability, clearAvailability, isLoading } = + useAvailability(accessToken ?? "", interceptors) + + const sCode = skuCode ?? lineItem?.sku_code ?? sku?.code + + useEffect(() => { + if ( + accessToken != null && + accessToken !== "" && + (sCode != null || skuId != null) + ) { + fetchAvailability({ skuCode: sCode, skuId }) + } + return () => { + clearAvailability() + } + }, [accessToken, sCode, skuId, clearAvailability, fetchAvailability]) + + useEffect(() => { + if (getQuantity != null && availability?.quantity != null) { + getQuantity(availability.quantity) + } + }, [availability?.quantity, getQuantity]) + + const contextValue = useMemo( + () => ({ ...availability, parent: true }), + [availability], + ) + + const hasFetchTarget = sCode != null || skuId != null + if (hasFetchTarget && isLoading) return propLoader + + return ( + + {children} + + ) +} + +export default Availability diff --git a/packages/react-components/src/components/skus/AvailabilityContainer.tsx b/packages/react-components/src/components/skus/AvailabilityContainer.tsx index 33e9102e..29d34039 100644 --- a/packages/react-components/src/components/skus/AvailabilityContainer.tsx +++ b/packages/react-components/src/components/skus/AvailabilityContainer.tsx @@ -37,6 +37,10 @@ interface Props { * * `` * + * + * @deprecated Use `` instead. It provides the same functionality + * without requiring a "container" wrapper. `AvailabilityContainer` remains + * functional for backwards compatibility. */ export function AvailabilityContainer({ children, diff --git a/packages/react-components/src/index.ts b/packages/react-components/src/index.ts index 4e2f7761..9b96c9ac 100644 --- a/packages/react-components/src/index.ts +++ b/packages/react-components/src/index.ts @@ -93,6 +93,7 @@ export * from "#components/shipping_methods/ShippingMethod" export * from "#components/shipping_methods/ShippingMethodName" export * from "#components/shipping_methods/ShippingMethodPrice" export * from "#components/shipping_methods/ShippingMethodRadioButton" +export * from "#components/skus/Availability" export * from "#components/skus/AvailabilityContainer" export * from "#components/skus/AvailabilityTemplate" export * from "#components/skus/DeliveryLeadTime" From 9a9fef779f90e0db99a543d3208dd0c455573807 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Wed, 29 Apr 2026 17:07:52 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=9D=20docs(availability):=20add=20?= =?UTF-8?q?standalone=20Availability=20section=20and=20deprecation=20notic?= =?UTF-8?q?e=20for=20AvailabilityContainer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stories/availability/001.availability.mdx | 88 +++++++++++++------ 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/packages/document/src/stories/availability/001.availability.mdx b/packages/document/src/stories/availability/001.availability.mdx index a0e10d26..95dbac63 100644 --- a/packages/document/src/stories/availability/001.availability.mdx +++ b/packages/document/src/stories/availability/001.availability.mdx @@ -12,15 +12,13 @@ All Availability components must be nested inside the `` context. --- -## AvailabilityContainer +## Availability (standalone) -`AvailabilityContainer` is the root component of the Availability tree. -It fetches inventory data for a given SKU (by code or ID) and exposes the result -to its children through the Availability context. +The preferred way to display availability. `` fetches inventory data on its own — +no container wrapper needed. - + Must be a child of the `` component. -Can also be a child of `` inside a ``, in which case `skuCode` is inherited automatically. @@ -34,6 +32,7 @@ Can also be a child of `` inside a ``, in which case `skuCo | `skuCode` | `string` | — | The SKU code to fetch availability for | | `skuId` | `string` | — | The SKU ID (takes precedence over `skuCode`; improves performance) | | `getQuantity` | `(quantity: number) => void` | — | Callback fired whenever the available quantity changes | +| `loader` | `ReactNode` | — | Content shown while fetching (default: `"Loading..."`) | ` inside a ``, in which case `skuCo code={` import { CommerceLayer, - AvailabilityContainer, + Availability, AvailabilityTemplate, } from '@commercelayer/react-components' - - - + + + `} /> --- +## AvailabilityContainer + + +`AvailabilityContainer` is deprecated. Use the standalone `` component instead (see above). + + +**Migration guide** + +Before (deprecated): + + + + +`} +/> + +After (preferred): + + + + +`} +/> + +--- + ## AvailabilityTemplate -`AvailabilityTemplate` reads from the parent `AvailabilityContainer` context and renders +`AvailabilityTemplate` reads from the parent `Availability` (or `AvailabilityContainer`) context and renders a `` with availability text. You can customise the label shown for each state (`available`, `outOfStock`, `negativeStock`) and optionally include delivery lead time and shipping method details. -Must be a descendant of the `` component. +Must be a descendant of the `` component. **Props** @@ -81,7 +114,7 @@ Must be a descendant of the `` component. language="jsx" dark code={` - + ` component. showShippingMethodName showShippingMethodPrice /> - + `} /> @@ -105,7 +138,7 @@ The function receives the full availability context including `quantity`, `text` language="jsx" dark code={` - + {({ quantity, text, min, max }) => (
@@ -116,15 +149,15 @@ The function receives the full availability context including `quantity`, `text`
)}
-
+ `} /> --- -## Usage inside SkusContainer +## Usage inside Skus -When used inside a `` → `` tree, `AvailabilityContainer` +When used inside a `` or `` → `` tree, `Availability` automatically inherits the `skuCode` from the current SKU context — no need to pass `skuCode` explicitly. @@ -134,22 +167,19 @@ no need to pass `skuCode` explicitly. code={` import { CommerceLayer, - SkusContainer, - Skus, + Sku, SkuField, - AvailabilityContainer, + Availability, AvailabilityTemplate, } from '@commercelayer/react-components' - - - - - - - - + + + + + + `} /> From d536353792d489c20954976bd9ff7ef410495e02 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Wed, 29 Apr 2026 17:39:18 +0200 Subject: [PATCH 3/3] =?UTF-8?q?docs(availability):=20=F0=9F=94=84=20replac?= =?UTF-8?q?e=20container=20stories=20with=20standalone=20Availability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace all AvailabilityContainer stories with standalone Availability - Add InsideSku story showing Availability inheriting skuCode from Sku - Keep one DeprecatedContainer story as legacy reference - Remove Skus/SkusContainer imports --- .../availability/availability.stories.tsx | 85 +++++++++---------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/packages/document/src/stories/availability/availability.stories.tsx b/packages/document/src/stories/availability/availability.stories.tsx index 9df37ec3..a9c3f00f 100644 --- a/packages/document/src/stories/availability/availability.stories.tsx +++ b/packages/document/src/stories/availability/availability.stories.tsx @@ -2,9 +2,8 @@ import { Availability, AvailabilityContainer, AvailabilityTemplate, + Sku, SkuField, - Skus, - SkusContainer, } from "@commercelayer/react-components" import type { Meta, StoryObj } from "@storybook/react-vite" import CommerceLayer from "../_internals/CommerceLayer" @@ -32,22 +31,11 @@ export const StandaloneAvailability: Story = { ), } -export const BasicAvailability: Story = { - name: "AvailabilityContainer — basic", - render: () => ( - - - - - - ), -} - export const CustomLabels: Story = { name: "AvailabilityTemplate — custom labels", render: () => ( - + - + ), } @@ -64,12 +52,12 @@ export const WithDeliveryLeadTimeDays: Story = { name: "AvailabilityTemplate — lead time in days", render: () => ( - + - + ), } @@ -78,12 +66,12 @@ export const WithDeliveryLeadTimeHours: Story = { name: "AvailabilityTemplate — lead time in hours", render: () => ( - + - + ), } @@ -92,9 +80,9 @@ export const WithShippingMethodName: Story = { name: "AvailabilityTemplate — with shipping method name", render: () => ( - + - + ), } @@ -103,29 +91,29 @@ export const WithShippingMethodPrice: Story = { name: "AvailabilityTemplate — with shipping method price", render: () => ( - + - + ), } export const WithGetQuantityCallback: Story = { - name: "AvailabilityContainer — getQuantity callback", + name: "Availability — getQuantity callback", render: () => ( - { console.log("quantity updated:", quantity) }} > - + ), } @@ -134,7 +122,7 @@ export const WithChildrenRenderProp: Story = { name: "AvailabilityTemplate — children render prop", render: () => ( - + {({ quantity, text, min, max }) => (
@@ -147,31 +135,36 @@ export const WithChildrenRenderProp: Story = {
)}
-
+
), } -export const InsideSkusContainer: Story = { - name: "AvailabilityContainer — inside SkusContainer", +export const InsideSku: Story = { + name: "Availability — inside Sku (inherits skuCode)", render: () => ( - - -
- - - - -
-
-
+ + + + + + +
+ ), +} + +export const DeprecatedContainer: Story = { + name: "AvailabilityContainer — deprecated (legacy)", + render: () => ( + + + + ), }