@vtex/faststore 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/actions.json +334 -0
- package/dist/catalog.json +6615 -0
- package/dist/client.d.ts +17 -0
- package/dist/client.js +8 -0
- package/dist/components.d.ts +87 -0
- package/dist/components.js +87 -0
- package/dist/content-types.json +684 -0
- package/dist/core/dist/actions/mock-action.d.ts +24 -0
- package/dist/core/dist/actions/mock-action.js +28 -0
- package/dist/core/dist/actions/mock-action.js.map +1 -0
- package/dist/core/dist/actions/registry.d.ts +24 -0
- package/dist/core/dist/actions/registry.js +28 -0
- package/dist/core/dist/actions/registry.js.map +1 -0
- package/dist/core/dist/define-component.d.ts +100 -0
- package/dist/core/dist/define-component.js +8 -0
- package/dist/core/dist/define-component.js.map +1 -0
- package/dist/core/dist/define-content-type.d.ts +95 -0
- package/dist/core/dist/define-content-type.js +283 -0
- package/dist/core/dist/define-content-type.js.map +1 -0
- package/dist/core/dist/index.d.ts +7 -0
- package/dist/core/dist/index.js +5 -0
- package/dist/core/dist/schema/generated/schema.d.ts +28 -0
- package/dist/core/dist/types.d.ts +1 -0
- package/dist/docs/access-code-field.md +100 -0
- package/dist/docs/access-code-sign-in.md +174 -0
- package/dist/docs/account-entry-point.md +155 -0
- package/dist/docs/address-summary.md +50 -0
- package/dist/docs/buy-button.md +162 -0
- package/dist/docs/carousel.md +105 -0
- package/dist/docs/cart-item.md +162 -0
- package/dist/docs/cart-totalizer.md +197 -0
- package/dist/docs/cart.md +495 -0
- package/dist/docs/client-preferences.md +184 -0
- package/dist/docs/client-profile-form.md +192 -0
- package/dist/docs/client-profile.md +117 -0
- package/dist/docs/container.md +97 -0
- package/dist/docs/document-field.md +97 -0
- package/dist/docs/email-field.md +93 -0
- package/dist/docs/email-identification.md +102 -0
- package/dist/docs/footer-copyright.md +112 -0
- package/dist/docs/footer-link-group.md +162 -0
- package/dist/docs/footer-root.md +145 -0
- package/dist/docs/footer-social.md +151 -0
- package/dist/docs/free-shipping-progress.md +87 -0
- package/dist/docs/gallery.md +84 -0
- package/dist/docs/header-root.md +150 -0
- package/dist/docs/identity-edit.md +88 -0
- package/dist/docs/identity-summary.md +106 -0
- package/dist/docs/incentives.md +151 -0
- package/dist/docs/logo.md +135 -0
- package/dist/docs/meter.md +67 -0
- package/dist/docs/navigation-link-list.md +171 -0
- package/dist/docs/navigation-link.md +163 -0
- package/dist/docs/navigation-menu.md +264 -0
- package/dist/docs/payment-methods.md +139 -0
- package/dist/docs/phone-field.md +106 -0
- package/dist/docs/postal-code-field.md +126 -0
- package/dist/docs/pricing.md +499 -0
- package/dist/docs/product-gallery.md +409 -0
- package/dist/docs/product-information.md +295 -0
- package/dist/docs/product-list.md +170 -0
- package/dist/docs/product-quantity.md +88 -0
- package/dist/docs/product-shelf.md +141 -0
- package/dist/docs/promotion.md +109 -0
- package/dist/docs/region.md +225 -0
- package/dist/docs/session.md +123 -0
- package/dist/docs/shipping-address.md +390 -0
- package/dist/docs/shipping-simulator.md +147 -0
- package/dist/docs/sign-in-form.md +117 -0
- package/dist/docs/tabs.md +176 -0
- package/dist/docs/toast.md +124 -0
- package/dist/docs/variant-selector.md +450 -0
- package/dist/graphql/define-subgraph.d.ts +12 -0
- package/dist/graphql/define-subgraph.js +15 -0
- package/dist/graphql/define-subgraph.js.map +1 -0
- package/dist/graphql/gql.d.ts +10 -0
- package/dist/graphql/gql.js +16 -0
- package/dist/graphql/gql.js.map +1 -0
- package/dist/graphql/types.d.ts +88 -0
- package/dist/graphql.d.ts +4 -0
- package/dist/graphql.js +3 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +5 -0
- package/dist/internal/client.d.ts +24 -0
- package/dist/internal/client.js +23 -0
- package/dist/internal/content.d.ts +2 -0
- package/dist/internal/content.js +2 -0
- package/dist/internal/server.d.ts +7 -0
- package/dist/internal/server.js +6 -0
- package/dist/matter/access-code-sign-in/access-code-sign-in.d.ts +111 -0
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +387 -0
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.d.ts +64 -0
- package/dist/matter/account-entry-point/account-entry-point.js +151 -0
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -0
- package/dist/matter/breadcrumb/breadcrumb.d.ts +40 -0
- package/dist/matter/breadcrumb/breadcrumb.js +155 -0
- package/dist/matter/breadcrumb/breadcrumb.js.map +1 -0
- package/dist/matter/buy-button/buy-button.d.ts +95 -0
- package/dist/matter/buy-button/buy-button.js +259 -0
- package/dist/matter/buy-button/buy-button.js.map +1 -0
- package/dist/matter/cart/cart-namespace.d.ts +48 -0
- package/dist/matter/cart/cart-namespace.js +50 -0
- package/dist/matter/cart/cart-namespace.js.map +1 -0
- package/dist/matter/cart/cart.d.ts +312 -0
- package/dist/matter/cart/cart.js +1034 -0
- package/dist/matter/cart/cart.js.map +1 -0
- package/dist/matter/cart-item/cart-item.d.ts +103 -0
- package/dist/matter/cart-item/cart-item.js +672 -0
- package/dist/matter/cart-item/cart-item.js.map +1 -0
- package/dist/matter/cart-item-compositions/cart-item-compositions.d.ts +49 -0
- package/dist/matter/cart-item-compositions/cart-item-compositions.js +165 -0
- package/dist/matter/cart-item-compositions/cart-item-compositions.js.map +1 -0
- package/dist/matter/cart-item-customizations/cart-item-customizations.d.ts +28 -0
- package/dist/matter/cart-item-customizations/cart-item-customizations.js +73 -0
- package/dist/matter/cart-item-customizations/cart-item-customizations.js.map +1 -0
- package/dist/matter/cart-item-discounts/cart-item-discounts.d.ts +33 -0
- package/dist/matter/cart-item-discounts/cart-item-discounts.js +70 -0
- package/dist/matter/cart-item-discounts/cart-item-discounts.js.map +1 -0
- package/dist/matter/cart-item-seller-info/cart-item-seller-info.d.ts +24 -0
- package/dist/matter/cart-item-seller-info/cart-item-seller-info.js +54 -0
- package/dist/matter/cart-item-seller-info/cart-item-seller-info.js.map +1 -0
- package/dist/matter/cart-item-variations/cart-item-variations.d.ts +27 -0
- package/dist/matter/cart-item-variations/cart-item-variations.js +69 -0
- package/dist/matter/cart-item-variations/cart-item-variations.js.map +1 -0
- package/dist/matter/cart-totalizer/cart-totalizer.d.ts +78 -0
- package/dist/matter/cart-totalizer/cart-totalizer.js +220 -0
- package/dist/matter/cart-totalizer/cart-totalizer.js.map +1 -0
- package/dist/matter/client-preferences/client-preferences.d.ts +50 -0
- package/dist/matter/client-preferences/client-preferences.js +206 -0
- package/dist/matter/client-preferences/client-preferences.js.map +1 -0
- package/dist/matter/client-profile/client-profile.d.ts +59 -0
- package/dist/matter/client-profile/client-profile.js +185 -0
- package/dist/matter/client-profile/client-profile.js.map +1 -0
- package/dist/matter/client-profile-form/client-profile-form.d.ts +126 -0
- package/dist/matter/client-profile-form/client-profile-form.js +660 -0
- package/dist/matter/client-profile-form/client-profile-form.js.map +1 -0
- package/dist/matter/delivery-options/delivery-options-accordion.d.ts +30 -0
- package/dist/matter/delivery-options/delivery-options-accordion.js +189 -0
- package/dist/matter/delivery-options/delivery-options-accordion.js.map +1 -0
- package/dist/matter/delivery-options/delivery-options-controller.d.ts +49 -0
- package/dist/matter/delivery-options/delivery-options-controller.js +203 -0
- package/dist/matter/delivery-options/delivery-options-controller.js.map +1 -0
- package/dist/matter/delivery-options/delivery-options.d.ts +31 -0
- package/dist/matter/delivery-options/delivery-options.js +103 -0
- package/dist/matter/delivery-options/delivery-options.js.map +1 -0
- package/dist/matter/delivery-options/use-delivery-option-selection.d.ts +9 -0
- package/dist/matter/delivery-options/use-delivery-option-selection.js +76 -0
- package/dist/matter/delivery-options/use-delivery-option-selection.js.map +1 -0
- package/dist/matter/email-identification/email-identification.d.ts +37 -0
- package/dist/matter/email-identification/email-identification.js +111 -0
- package/dist/matter/email-identification/email-identification.js.map +1 -0
- package/dist/matter/free-shipping-progress/free-shipping-progress.d.ts +44 -0
- package/dist/matter/free-shipping-progress/free-shipping-progress.js +170 -0
- package/dist/matter/free-shipping-progress/free-shipping-progress.js.map +1 -0
- package/dist/matter/identity-edit/identity-edit.d.ts +52 -0
- package/dist/matter/identity-edit/identity-edit.js +148 -0
- package/dist/matter/identity-edit/identity-edit.js.map +1 -0
- package/dist/matter/identity-summary/identity-summary.d.ts +128 -0
- package/dist/matter/identity-summary/identity-summary.js +344 -0
- package/dist/matter/identity-summary/identity-summary.js.map +1 -0
- package/dist/matter/pricing/pricing.d.ts +162 -0
- package/dist/matter/pricing/pricing.js +793 -0
- package/dist/matter/pricing/pricing.js.map +1 -0
- package/dist/matter/product-gallery/product-gallery.d.ts +23 -0
- package/dist/matter/product-gallery/product-gallery.js +188 -0
- package/dist/matter/product-gallery/product-gallery.js.map +1 -0
- package/dist/matter/product-information/product-information.d.ts +61 -0
- package/dist/matter/product-information/product-information.js +355 -0
- package/dist/matter/product-information/product-information.js.map +1 -0
- package/dist/matter/product-list/product-list.d.ts +86 -0
- package/dist/matter/product-list/product-list.js +342 -0
- package/dist/matter/product-list/product-list.js.map +1 -0
- package/dist/matter/product-quantity/product-quantity.d.ts +50 -0
- package/dist/matter/product-quantity/product-quantity.js +111 -0
- package/dist/matter/product-quantity/product-quantity.js.map +1 -0
- package/dist/matter/product-shelf/product-shelf.d.ts +125 -0
- package/dist/matter/product-shelf/product-shelf.js +376 -0
- package/dist/matter/product-shelf/product-shelf.js.map +1 -0
- package/dist/matter/region/region.d.ts +211 -0
- package/dist/matter/region/region.js +579 -0
- package/dist/matter/region/region.js.map +1 -0
- package/dist/matter/session/session.d.ts +58 -0
- package/dist/matter/session/session.js +112 -0
- package/dist/matter/session/session.js.map +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +101 -0
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -0
- package/dist/matter/shipping-address/shipping-address.d.ts +284 -0
- package/dist/matter/shipping-address/shipping-address.js +1036 -0
- package/dist/matter/shipping-address/shipping-address.js.map +1 -0
- package/dist/matter/shipping-simulator/shipping-simulator.d.ts +143 -0
- package/dist/matter/shipping-simulator/shipping-simulator.js +644 -0
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -0
- package/dist/matter/sign-in-form/sign-in-form.d.ts +79 -0
- package/dist/matter/sign-in-form/sign-in-form.js +226 -0
- package/dist/matter/sign-in-form/sign-in-form.js.map +1 -0
- package/dist/matter/variant-selector/variant-selector.d.ts +183 -0
- package/dist/matter/variant-selector/variant-selector.js +840 -0
- package/dist/matter/variant-selector/variant-selector.js.map +1 -0
- package/dist/message-locales.json +7 -0
- package/dist/particles/access-code-field/access-code-field.d.ts +33 -0
- package/dist/particles/access-code-field/access-code-field.js +95 -0
- package/dist/particles/access-code-field/access-code-field.js.map +1 -0
- package/dist/particles/accordion/accordion.d.ts +45 -0
- package/dist/particles/accordion/accordion.js +156 -0
- package/dist/particles/accordion/accordion.js.map +1 -0
- package/dist/particles/address-summary/address-summary.d.ts +50 -0
- package/dist/particles/address-summary/address-summary.js +129 -0
- package/dist/particles/address-summary/address-summary.js.map +1 -0
- package/dist/particles/badge/badge.d.ts +10 -0
- package/dist/particles/badge/badge.js +27 -0
- package/dist/particles/badge/badge.js.map +1 -0
- package/dist/particles/button/button.d.ts +18 -0
- package/dist/particles/button/button.js +37 -0
- package/dist/particles/button/button.js.map +1 -0
- package/dist/particles/carousel/carousel.d.ts +60 -0
- package/dist/particles/carousel/carousel.js +335 -0
- package/dist/particles/carousel/carousel.js.map +1 -0
- package/dist/particles/checkbox/checkbox.d.ts +40 -0
- package/dist/particles/checkbox/checkbox.js +82 -0
- package/dist/particles/checkbox/checkbox.js.map +1 -0
- package/dist/particles/container/container.d.ts +20 -0
- package/dist/particles/container/container.js +36 -0
- package/dist/particles/container/container.js.map +1 -0
- package/dist/particles/dialog/dialog.d.ts +54 -0
- package/dist/particles/dialog/dialog.js +192 -0
- package/dist/particles/dialog/dialog.js.map +1 -0
- package/dist/particles/document-field/document-field.d.ts +61 -0
- package/dist/particles/document-field/document-field.js +146 -0
- package/dist/particles/document-field/document-field.js.map +1 -0
- package/dist/particles/drawer/drawer.d.ts +52 -0
- package/dist/particles/drawer/drawer.js +183 -0
- package/dist/particles/drawer/drawer.js.map +1 -0
- package/dist/particles/email-field/email-field.d.ts +37 -0
- package/dist/particles/email-field/email-field.js +106 -0
- package/dist/particles/email-field/email-field.js.map +1 -0
- package/dist/particles/field/field.d.ts +50 -0
- package/dist/particles/field/field.js +133 -0
- package/dist/particles/field/field.js.map +1 -0
- package/dist/particles/fieldset/fieldset.d.ts +16 -0
- package/dist/particles/fieldset/fieldset.js +52 -0
- package/dist/particles/fieldset/fieldset.js.map +1 -0
- package/dist/particles/footer-copyright/footer-copyright.d.ts +32 -0
- package/dist/particles/footer-copyright/footer-copyright.js +50 -0
- package/dist/particles/footer-copyright/footer-copyright.js.map +1 -0
- package/dist/particles/footer-link-group/footer-link-group.d.ts +66 -0
- package/dist/particles/footer-link-group/footer-link-group.js +154 -0
- package/dist/particles/footer-link-group/footer-link-group.js.map +1 -0
- package/dist/particles/footer-root/footer-root.d.ts +21 -0
- package/dist/particles/footer-root/footer-root.js +38 -0
- package/dist/particles/footer-root/footer-root.js.map +1 -0
- package/dist/particles/footer-social/footer-social.d.ts +79 -0
- package/dist/particles/footer-social/footer-social.js +176 -0
- package/dist/particles/footer-social/footer-social.js.map +1 -0
- package/dist/particles/gallery/gallery.d.ts +181 -0
- package/dist/particles/gallery/gallery.js +920 -0
- package/dist/particles/gallery/gallery.js.map +1 -0
- package/dist/particles/header-root/header-root.d.ts +21 -0
- package/dist/particles/header-root/header-root.js +38 -0
- package/dist/particles/header-root/header-root.js.map +1 -0
- package/dist/particles/icon/icon.d.ts +22 -0
- package/dist/particles/icon/icon.js +48 -0
- package/dist/particles/icon/icon.js.map +1 -0
- package/dist/particles/image/image.d.ts +13 -0
- package/dist/particles/image/image.js +59 -0
- package/dist/particles/image/image.js.map +1 -0
- package/dist/particles/incentives/incentives.d.ts +85 -0
- package/dist/particles/incentives/incentives.js +200 -0
- package/dist/particles/incentives/incentives.js.map +1 -0
- package/dist/particles/input/input.d.ts +9 -0
- package/dist/particles/input/input.js +28 -0
- package/dist/particles/input/input.js.map +1 -0
- package/dist/particles/logo/logo.d.ts +55 -0
- package/dist/particles/logo/logo.js +96 -0
- package/dist/particles/logo/logo.js.map +1 -0
- package/dist/particles/menu/menu.d.ts +103 -0
- package/dist/particles/menu/menu.js +409 -0
- package/dist/particles/menu/menu.js.map +1 -0
- package/dist/particles/menubar/menubar.d.ts +18 -0
- package/dist/particles/menubar/menubar.js +42 -0
- package/dist/particles/menubar/menubar.js.map +1 -0
- package/dist/particles/meter/meter.d.ts +36 -0
- package/dist/particles/meter/meter.js +132 -0
- package/dist/particles/meter/meter.js.map +1 -0
- package/dist/particles/navigation-link/navigation-link.d.ts +43 -0
- package/dist/particles/navigation-link/navigation-link.js +82 -0
- package/dist/particles/navigation-link/navigation-link.js.map +1 -0
- package/dist/particles/navigation-link-list/navigation-link-list.d.ts +59 -0
- package/dist/particles/navigation-link-list/navigation-link-list.js +103 -0
- package/dist/particles/navigation-link-list/navigation-link-list.js.map +1 -0
- package/dist/particles/navigation-menu/navigation-menu.d.ts +78 -0
- package/dist/particles/navigation-menu/navigation-menu.js +295 -0
- package/dist/particles/navigation-menu/navigation-menu.js.map +1 -0
- package/dist/particles/number-field/number-field.d.ts +46 -0
- package/dist/particles/number-field/number-field.js +169 -0
- package/dist/particles/number-field/number-field.js.map +1 -0
- package/dist/particles/password-field/password-field.d.ts +31 -0
- package/dist/particles/password-field/password-field.js +109 -0
- package/dist/particles/password-field/password-field.js.map +1 -0
- package/dist/particles/payment-methods/payment-methods.d.ts +72 -0
- package/dist/particles/payment-methods/payment-methods.js +151 -0
- package/dist/particles/payment-methods/payment-methods.js.map +1 -0
- package/dist/particles/phone-field/phone-field.d.ts +67 -0
- package/dist/particles/phone-field/phone-field.js +250 -0
- package/dist/particles/phone-field/phone-field.js.map +1 -0
- package/dist/particles/popover/popover.d.ts +68 -0
- package/dist/particles/popover/popover.js +284 -0
- package/dist/particles/popover/popover.js.map +1 -0
- package/dist/particles/postal-code-field/postal-code-field.d.ts +75 -0
- package/dist/particles/postal-code-field/postal-code-field.js +169 -0
- package/dist/particles/postal-code-field/postal-code-field.js.map +1 -0
- package/dist/particles/promotion/promotion.d.ts +46 -0
- package/dist/particles/promotion/promotion.js +78 -0
- package/dist/particles/promotion/promotion.js.map +1 -0
- package/dist/particles/quantity-selector/quantity-selector.d.ts +30 -0
- package/dist/particles/quantity-selector/quantity-selector.js +116 -0
- package/dist/particles/quantity-selector/quantity-selector.js.map +1 -0
- package/dist/particles/radio/radio.d.ts +36 -0
- package/dist/particles/radio/radio.js +76 -0
- package/dist/particles/radio/radio.js.map +1 -0
- package/dist/particles/scroll-area/scroll-area.d.ts +36 -0
- package/dist/particles/scroll-area/scroll-area.js +145 -0
- package/dist/particles/scroll-area/scroll-area.js.map +1 -0
- package/dist/particles/select/select.d.ts +125 -0
- package/dist/particles/select/select.js +478 -0
- package/dist/particles/select/select.js.map +1 -0
- package/dist/particles/separator/separator.d.ts +12 -0
- package/dist/particles/separator/separator.js +33 -0
- package/dist/particles/separator/separator.js.map +1 -0
- package/dist/particles/shared/input-mask.d.ts +5 -0
- package/dist/particles/shared/input-mask.js +58 -0
- package/dist/particles/shared/input-mask.js.map +1 -0
- package/dist/particles/spinner/spinner.d.ts +13 -0
- package/dist/particles/spinner/spinner.js +31 -0
- package/dist/particles/spinner/spinner.js.map +1 -0
- package/dist/particles/switch/switch.d.ts +27 -0
- package/dist/particles/switch/switch.js +59 -0
- package/dist/particles/switch/switch.js.map +1 -0
- package/dist/particles/tabs/tabs.d.ts +45 -0
- package/dist/particles/tabs/tabs.js +164 -0
- package/dist/particles/tabs/tabs.js.map +1 -0
- package/dist/particles/text/text.d.ts +24 -0
- package/dist/particles/text/text.js +40 -0
- package/dist/particles/text/text.js.map +1 -0
- package/dist/particles/toast/toast.d.ts +83 -0
- package/dist/particles/toast/toast.js +194 -0
- package/dist/particles/toast/toast.js.map +1 -0
- package/dist/particles/toggle/toggle.d.ts +26 -0
- package/dist/particles/toggle/toggle.js +61 -0
- package/dist/particles/toggle/toggle.js.map +1 -0
- package/dist/particles/tooltip/tooltip.d.ts +61 -0
- package/dist/particles/tooltip/tooltip.js +240 -0
- package/dist/particles/tooltip/tooltip.js.map +1 -0
- package/dist/schema.graphql +2953 -0
- package/dist/sdk/actions/cart-action-provider.d.ts +19 -0
- package/dist/sdk/actions/cart-action-provider.js +23 -0
- package/dist/sdk/actions/cart-action-provider.js.map +1 -0
- package/dist/sdk/actions/identity-action-provider.d.ts +15 -0
- package/dist/sdk/actions/identity-action-provider.js +19 -0
- package/dist/sdk/actions/identity-action-provider.js.map +1 -0
- package/dist/sdk/actions/index.d.ts +7 -0
- package/dist/sdk/actions/index.js +6 -0
- package/dist/sdk/actions/inject-actions.d.ts +14 -0
- package/dist/sdk/actions/inject-actions.js +36 -0
- package/dist/sdk/actions/inject-actions.js.map +1 -0
- package/dist/sdk/actions/region-action-provider.d.ts +15 -0
- package/dist/sdk/actions/region-action-provider.js +19 -0
- package/dist/sdk/actions/region-action-provider.js.map +1 -0
- package/dist/sdk/actions/session-action-provider.d.ts +15 -0
- package/dist/sdk/actions/session-action-provider.js +19 -0
- package/dist/sdk/actions/session-action-provider.js.map +1 -0
- package/dist/sdk/actions/shipping-action-provider.d.ts +15 -0
- package/dist/sdk/actions/shipping-action-provider.js +19 -0
- package/dist/sdk/actions/shipping-action-provider.js.map +1 -0
- package/dist/sdk/actions/types.d.ts +21 -0
- package/dist/sdk/cache/storefront-cache.js +35 -0
- package/dist/sdk/cache/storefront-cache.js.map +1 -0
- package/dist/sdk/cart/operations.d.ts +31 -0
- package/dist/sdk/cart/operations.js +215 -0
- package/dist/sdk/cart/operations.js.map +1 -0
- package/dist/sdk/cart/optimistic.d.ts +24 -0
- package/dist/sdk/cart/optimistic.js +164 -0
- package/dist/sdk/cart/optimistic.js.map +1 -0
- package/dist/sdk/cart/use-cart-actions.d.ts +56 -0
- package/dist/sdk/cart/use-cart-actions.js +348 -0
- package/dist/sdk/cart/use-cart-actions.js.map +1 -0
- package/dist/sdk/cart/use-cart-validate-on-open.js +50 -0
- package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -0
- package/dist/sdk/cart/write-gate.js +54 -0
- package/dist/sdk/cart/write-gate.js.map +1 -0
- package/dist/sdk/client/assert-api-url.js +20 -0
- package/dist/sdk/client/assert-api-url.js.map +1 -0
- package/dist/sdk/client/create-client.d.ts +23 -0
- package/dist/sdk/client/create-client.js +46 -0
- package/dist/sdk/client/create-client.js.map +1 -0
- package/dist/sdk/client/fetch-page-data.d.ts +39 -0
- package/dist/sdk/client/fetch-page-data.js +33 -0
- package/dist/sdk/client/fetch-page-data.js.map +1 -0
- package/dist/sdk/client/register-preload-query.d.ts +24 -0
- package/dist/sdk/client/register-preload-query.js +33 -0
- package/dist/sdk/client/register-preload-query.js.map +1 -0
- package/dist/sdk/content/create-content-runtime.d.ts +68 -0
- package/dist/sdk/content/create-content-runtime.js +503 -0
- package/dist/sdk/content/create-content-runtime.js.map +1 -0
- package/dist/sdk/context/headers.d.ts +15 -0
- package/dist/sdk/context/headers.js +21 -0
- package/dist/sdk/context/headers.js.map +1 -0
- package/dist/sdk/context/storefront-context.d.ts +11 -0
- package/dist/sdk/context/storefront-context.js +29 -0
- package/dist/sdk/context/storefront-context.js.map +1 -0
- package/dist/sdk/data/data-override-context.d.ts +12 -0
- package/dist/sdk/data/data-override-context.js +19 -0
- package/dist/sdk/data/data-override-context.js.map +1 -0
- package/dist/sdk/data/entity-subscriber.js +28 -0
- package/dist/sdk/data/entity-subscriber.js.map +1 -0
- package/dist/sdk/data/hot-data-provider.d.ts +19 -0
- package/dist/sdk/data/hot-data-provider.js +33 -0
- package/dist/sdk/data/hot-data-provider.js.map +1 -0
- package/dist/sdk/data/index.d.ts +8 -0
- package/dist/sdk/data/index.js +8 -0
- package/dist/sdk/data/list-item-ref-context.d.ts +20 -0
- package/dist/sdk/data/list-item-ref-context.js +11 -0
- package/dist/sdk/data/list-item-ref-context.js.map +1 -0
- package/dist/sdk/data/multiple-reactive-data-provider.d.ts +32 -0
- package/dist/sdk/data/multiple-reactive-data-provider.js +20 -0
- package/dist/sdk/data/multiple-reactive-data-provider.js.map +1 -0
- package/dist/sdk/data/page-reactive-data-root.d.ts +53 -0
- package/dist/sdk/data/page-reactive-data-root.js +153 -0
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -0
- package/dist/sdk/data/reactive-data-provider.d.ts +25 -0
- package/dist/sdk/data/reactive-data-provider.js +24 -0
- package/dist/sdk/data/reactive-data-provider.js.map +1 -0
- package/dist/sdk/data/reactive-list-item-data-provider.d.ts +26 -0
- package/dist/sdk/data/reactive-list-item-data-provider.js +42 -0
- package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -0
- package/dist/sdk/data/use-reactive-entity.js +40 -0
- package/dist/sdk/data/use-reactive-entity.js.map +1 -0
- package/dist/sdk/format/client.d.ts +12 -0
- package/dist/sdk/format/client.js +20 -0
- package/dist/sdk/format/client.js.map +1 -0
- package/dist/sdk/format/formatters.d.ts +32 -0
- package/dist/sdk/format/formatters.js +45 -0
- package/dist/sdk/format/formatters.js.map +1 -0
- package/dist/sdk/generated/fragment-types.js +23 -0
- package/dist/sdk/generated/fragment-types.js.map +1 -0
- package/dist/sdk/generated/gql.js +51 -0
- package/dist/sdk/generated/gql.js.map +1 -0
- package/dist/sdk/generated/graphql.d.ts +622 -0
- package/dist/sdk/generated/graphql.js +11917 -0
- package/dist/sdk/generated/graphql.js.map +1 -0
- package/dist/sdk/graphql-tag.d.ts +10 -0
- package/dist/sdk/graphql-tag.js +21 -0
- package/dist/sdk/graphql-tag.js.map +1 -0
- package/dist/sdk/i18n/client.d.ts +4 -0
- package/dist/sdk/i18n/client.js +4 -0
- package/dist/sdk/i18n/locales.d.ts +8 -0
- package/dist/sdk/i18n/locales.js +57 -0
- package/dist/sdk/i18n/locales.js.map +1 -0
- package/dist/sdk/i18n/messages/en-US.js +24 -0
- package/dist/sdk/i18n/messages/en-US.js.map +1 -0
- package/dist/sdk/i18n/messages/en-US.json +19 -0
- package/dist/sdk/i18n/messages/en-US.json.d.ts +23 -0
- package/dist/sdk/i18n/messages/es-ES.js +24 -0
- package/dist/sdk/i18n/messages/es-ES.js.map +1 -0
- package/dist/sdk/i18n/messages/es-ES.json +19 -0
- package/dist/sdk/i18n/messages/pt-BR.js +24 -0
- package/dist/sdk/i18n/messages/pt-BR.js.map +1 -0
- package/dist/sdk/i18n/messages/pt-BR.json +19 -0
- package/dist/sdk/i18n/provider.d.ts +1 -0
- package/dist/sdk/i18n/provider.js +30 -0
- package/dist/sdk/i18n/provider.js.map +1 -0
- package/dist/sdk/i18n/server.d.ts +28 -0
- package/dist/sdk/i18n/server.js +59 -0
- package/dist/sdk/i18n/server.js.map +1 -0
- package/dist/sdk/identity/operations.d.ts +34 -0
- package/dist/sdk/identity/operations.js +78 -0
- package/dist/sdk/identity/operations.js.map +1 -0
- package/dist/sdk/identity/use-identity-actions.d.ts +62 -0
- package/dist/sdk/identity/use-identity-actions.js +180 -0
- package/dist/sdk/identity/use-identity-actions.js.map +1 -0
- package/dist/sdk/provider/index.d.ts +2 -0
- package/dist/sdk/provider/index.js +2 -0
- package/dist/sdk/provider/storefront-provider.d.ts +32 -0
- package/dist/sdk/provider/storefront-provider.js +111 -0
- package/dist/sdk/provider/storefront-provider.js.map +1 -0
- package/dist/sdk/region/operations.d.ts +6 -0
- package/dist/sdk/region/region-operations.d.ts +30 -0
- package/dist/sdk/region/region-operations.js +23 -0
- package/dist/sdk/region/region-operations.js.map +1 -0
- package/dist/sdk/region/use-region-actions.d.ts +23 -0
- package/dist/sdk/region/use-region-actions.js +66 -0
- package/dist/sdk/region/use-region-actions.js.map +1 -0
- package/dist/sdk/seller.js +11 -0
- package/dist/sdk/seller.js.map +1 -0
- package/dist/sdk/session/auth-operations.d.ts +47 -0
- package/dist/sdk/session/auth-operations.js +79 -0
- package/dist/sdk/session/auth-operations.js.map +1 -0
- package/dist/sdk/session/operations.d.ts +6 -0
- package/dist/sdk/session/use-session-actions.d.ts +27 -0
- package/dist/sdk/session/use-session-actions.js +126 -0
- package/dist/sdk/session/use-session-actions.js.map +1 -0
- package/dist/sdk/shipping/cart-operations.d.ts +3 -0
- package/dist/sdk/shipping/cart-operations.js +33 -0
- package/dist/sdk/shipping/cart-operations.js.map +1 -0
- package/dist/sdk/shipping/operations.d.ts +3 -0
- package/dist/sdk/shipping/operations.js +33 -0
- package/dist/sdk/shipping/operations.js.map +1 -0
- package/dist/sdk/shipping/option-select-operations.d.ts +24 -0
- package/dist/sdk/shipping/option-select-operations.js +45 -0
- package/dist/sdk/shipping/option-select-operations.js.map +1 -0
- package/dist/sdk/shipping/request-timeout.js +41 -0
- package/dist/sdk/shipping/request-timeout.js.map +1 -0
- package/dist/sdk/shipping/shipping-operations.d.ts +38 -0
- package/dist/sdk/shipping/shipping-operations.js +77 -0
- package/dist/sdk/shipping/shipping-operations.js.map +1 -0
- package/dist/sdk/shipping/use-cart-shipping-simulation.d.ts +7 -0
- package/dist/sdk/shipping/use-cart-shipping-simulation.js +72 -0
- package/dist/sdk/shipping/use-cart-shipping-simulation.js.map +1 -0
- package/dist/sdk/shipping/use-shipping-actions.d.ts +27 -0
- package/dist/sdk/shipping/use-shipping-actions.js +113 -0
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -0
- package/dist/sdk/shipping/use-shipping-simulation.d.ts +7 -0
- package/dist/sdk/shipping/use-shipping-simulation.js +72 -0
- package/dist/sdk/shipping/use-shipping-simulation.js.map +1 -0
- package/dist/sdk/state/cart.d.ts +42 -0
- package/dist/sdk/state/cart.js +64 -0
- package/dist/sdk/state/cart.js.map +1 -0
- package/dist/sdk/state/index.js +3 -0
- package/dist/sdk/state/variant-selector.d.ts +17 -0
- package/dist/sdk/state/variant-selector.js +14 -0
- package/dist/sdk/state/variant-selector.js.map +1 -0
- package/dist/utils/safe-href.js +63 -0
- package/dist/utils/safe-href.js.map +1 -0
- package/package.json +84 -0
- package/src/styles/tailwind.css +32 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# ProductShelf
|
|
2
|
+
|
|
3
|
+
**Category:** commerce
|
|
4
|
+
**Purpose:** A data-owning product shelf. `ProductShelfRoot` owns a facet-filtered `products(filter:)` query from the catalog module, receives its slice as `$data`, and publishes each product through a per-item context. `ProductShelfItems` repeats those products into a `Carousel` track, one card per slide. The card parts (`ProductCard*`) read each product from context — the shelf holds the data and config, the carousel arranges the rows, the cards render one product each.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
Use these signals to detect that a ProductShelf block is present. Position on the page is irrelevant for detection — a shelf can appear inline on a PDP (cross-sell), between hero and footer on the homepage, or below the results grid on a PLP.
|
|
11
|
+
|
|
12
|
+
A ProductShelf block contains any combination of:
|
|
13
|
+
|
|
14
|
+
- A **horizontal strip of three or more visually similar product cards**, each rendering the same template (product image + product name + product price, sometimes plus a list-price strikethrough or a discount chip).
|
|
15
|
+
- **Previous/Next arrows** (usually circular) sitting to the left and right of the strip — a carousel affordance for overflow items.
|
|
16
|
+
- **Carousel bullets / dots** below the strip indicating pages.
|
|
17
|
+
- A **section heading** naming the shelf's intent — e.g. *"Você também pode gostar"*, *"Compre também"*, *"Produtos similares"*, *"Você também vai gostar"*, *"Related products"*, *"You may also like"*, *"Customers also bought"*, *"Recommended for you"*.
|
|
18
|
+
|
|
19
|
+
If the image shows **any of the above**, use `ProductShelfRoot` composed with `CarouselRoot` and `ProductShelfItems`. When a section heading is visible, compose `ProductShelfHeading` inside `ProductShelfRoot` (above `CarouselRoot`) with the title as its `children` (like any `Text`). Pick only the card parts (`ProductCardImage`, `ProductCardName`, `ProductCardPrice`, `ProductCardListPrice`, `ProductCardDiscount`, …) that match what's visible in each card.
|
|
20
|
+
|
|
21
|
+
The image is the source of truth for the heading text, `first`/`itemsPerPage`, and which card parts to include. The **source of the shelf's products** (`categoryId` / `brandId` / `collectionId` / `searchTerm` / `specifications`) is a merchant configuration decision and cannot be read off the screenshot — leave those unset when the image gives no signal, and let the shelf fall back to catalog-wide relevance.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use
|
|
26
|
+
|
|
27
|
+
- A cross-sell / discovery shelf on the PDP (Related Products), PLP, or Search Results: a facet-scoped, navigable carousel of product cards configured entirely in the VTEX Studio.
|
|
28
|
+
|
|
29
|
+
## When NOT to use
|
|
30
|
+
|
|
31
|
+
- A full-page product listing driven by the route/search → use `ProductListRoot` (prop-driven) with the PLP grid.
|
|
32
|
+
- PDP single-product pricing → use `PricingRoot` (it fetches one `productBySlug`; it cannot price a per-card item in a list).
|
|
33
|
+
- A generic non-product track of children → use the `Carousel` particle directly.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Behavior model
|
|
38
|
+
|
|
39
|
+
- **The shelf owns the data.** `ProductShelfRoot` declares a `fragment ProductShelfData on Query` selecting `products(first: $first, orderBy: $orderBy, filter: $filter)` and receives the resolved slice as `$data`. `fetchStrategy: 'lazy'` defers the load (below-the-fold), and a skeleton shows while pending.
|
|
40
|
+
- **Config feeds the query.** The serializable schema props map to the fragment variables: `first` → `$first`, `sort` → `$orderBy` (`ProductSortingInput`), and the facet props (`searchTerm`, `categoryId`, `brandId`, `collectionId`, `specifications`, `availableOnly`) → `$filter` (`ProductsFilterInput`). `buildProductShelfVariables(config)` is the pure mapping.
|
|
41
|
+
- **Two context boundaries.** The shelf owns product data + config; each row owns one item. Card parts read the item through a guarded hook and never take list-shaped props.
|
|
42
|
+
- **Presence is state-driven.** Empty result → the shelf renders nothing (no empty carousel). Unresolved lazy source → skeleton.
|
|
43
|
+
- **The carousel is composed, not baked.** Place `CarouselRoot` with `ProductShelfItems` inside it, plus `CarouselPrevious` / `CarouselNext` / `CarouselBullets` where you want them. `ProductShelfItems` renders the `CarouselTrack`, so the carousel counts one slide per product.
|
|
44
|
+
- **The heading is composed, not baked.** `ProductShelfRoot` does not own the title; compose `ProductShelfHeading` where it should sit (usually above `CarouselRoot`) with the text as its `children`. It renders an `<h2>` and provides the section's `aria-labelledby` label. Omit it to render no heading.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Props
|
|
49
|
+
|
|
50
|
+
### ProductShelfRoot
|
|
51
|
+
|
|
52
|
+
| Prop | Type | Default | Notes |
|
|
53
|
+
|------|------|---------|-------|
|
|
54
|
+
| `first` | number | `5` | Max products fetched (legacy `numberOfItems`) → `$first` |
|
|
55
|
+
| `itemsPerPage` | number | `5` | Cards per page; bind to `CarouselRoot`'s `itemsPerPage` |
|
|
56
|
+
| `sort` | `relevance \| price_asc \| price_desc \| name_asc \| name_desc \| release \| orders \| discount` | `relevance` | → `$orderBy` |
|
|
57
|
+
| `searchTerm` | string | — | → `filter.searchTerm` |
|
|
58
|
+
| `categoryId` | string | — | → `filter.categoryIds` |
|
|
59
|
+
| `brandId` | string | — | → `filter.brandIds` |
|
|
60
|
+
| `collectionId` | string | — | → `filter.collectionId` (legacy `productClusterIds`) |
|
|
61
|
+
| `specifications` | `{ name, values }[]` | — | → `filter.specifications` (custom attributes) |
|
|
62
|
+
| `availableOnly` | boolean | `false` | → `filter.availableOnly` (global "hide out-of-stock") |
|
|
63
|
+
| `$data` | slice | — | Compiler-injected fragment result |
|
|
64
|
+
| `products` | `ProductShelfItem[]` | — | Pre-shaped items (stories/tests; bypasses `$data`) |
|
|
65
|
+
|
|
66
|
+
### ProductShelfItems
|
|
67
|
+
|
|
68
|
+
Repeats the shelf products into the carousel track, publishing one product per slide through the per-item context. Renders `CarouselTrack`, so place it directly inside `CarouselRoot`. Put the card template as its children. No props beyond standard `div` attributes.
|
|
69
|
+
|
|
70
|
+
### ProductShelfHeading
|
|
71
|
+
|
|
72
|
+
Renders its `children` as the shelf title — an `<h2>` (`data-fs-product-shelf-heading`) — and supplies the section's `aria-labelledby` label. Author the title text as its `children` (`props.children`), exactly like a `Text` element. Renders nothing when it has no children. Place it inside `ProductShelfRoot` where the title should appear (typically above `CarouselRoot`). No schema props beyond standard `h2` attributes.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Per-item context
|
|
77
|
+
|
|
78
|
+
`ProductShelfItems` publishes a `ProductShelfItem` per slide (the widened `ProductListItem`), wide enough for every card part:
|
|
79
|
+
|
|
80
|
+
| Field | Used by |
|
|
81
|
+
|-------|---------|
|
|
82
|
+
| `id`, `slug`, `name`, `image` | T3 image/title |
|
|
83
|
+
| `price`, `listPrice` | T3 selling/list price |
|
|
84
|
+
| `spotPrice` | T4 discount = `round((listPrice − spotPrice) × 100 / listPrice)` |
|
|
85
|
+
| `tax`, `taxPercentage` | T6 tax-inclusive price |
|
|
86
|
+
| `availableForSale`, `availableQuantity` | T5 out-of-stock state |
|
|
87
|
+
| `currency` | price formatting (falls back to the storefront context currency) |
|
|
88
|
+
|
|
89
|
+
Card parts read it via `useProductShelfItem()` (throws when used outside `ProductShelfItems`). The existing `ProductList.CardImage / CardName / CardPrice / CardListPrice` read the same context, so they compose unchanged.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Composition
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
ProductShelfRoot (owns data + config)
|
|
97
|
+
├── ProductShelfHeading (title as its children; optional)
|
|
98
|
+
└── CarouselRoot (itemsPerPage, infiniteMode, variant)
|
|
99
|
+
├── ProductShelfItems (repeats products; renders CarouselTrack)
|
|
100
|
+
│ └── <card template: ProductCardImage, ProductCardName, ProductCardPrice, ProductCardListPrice, …>
|
|
101
|
+
├── CarouselPrevious
|
|
102
|
+
├── CarouselNext
|
|
103
|
+
└── CarouselBullets
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Minimal schema example
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"shelf": {
|
|
113
|
+
"type": "ProductShelfRoot",
|
|
114
|
+
"props": { "first": 12, "itemsPerPage": 4, "sort": "relevance", "categoryId": "Category:42" },
|
|
115
|
+
"children": ["shelf-heading", "shelf-carousel"]
|
|
116
|
+
},
|
|
117
|
+
"shelf-heading": { "type": "ProductShelfHeading", "props": { "children": "You may also like" } },
|
|
118
|
+
"shelf-carousel": {
|
|
119
|
+
"type": "CarouselRoot",
|
|
120
|
+
"props": { "itemsPerPage": 4 },
|
|
121
|
+
"children": ["shelf-items", "shelf-prev", "shelf-next", "shelf-bullets"]
|
|
122
|
+
},
|
|
123
|
+
"shelf-items": { "type": "ProductShelfItems", "children": ["card-image", "card-name", "card-price"] },
|
|
124
|
+
"shelf-prev": { "type": "CarouselPrevious" },
|
|
125
|
+
"shelf-next": { "type": "CarouselNext" },
|
|
126
|
+
"shelf-bullets": { "type": "CarouselBullets" }
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Styling
|
|
133
|
+
|
|
134
|
+
All appearance is chosen by swatches keyed off the structural hooks:
|
|
135
|
+
|
|
136
|
+
- `data-fs-product-shelf` — the shelf section
|
|
137
|
+
- `data-fs-product-shelf-heading` — the heading
|
|
138
|
+
- `data-fs-product-shelf-items` — the items track (also carries `data-fs-carousel-track`)
|
|
139
|
+
- `data-fs-product-shelf-skeleton` / `data-fs-product-shelf-skeleton-item` — the loading skeleton
|
|
140
|
+
|
|
141
|
+
Card and carousel looks come from their own `data-fs-product-card-*` and `data-fs-carousel-*` hooks.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Promotion
|
|
2
|
+
|
|
3
|
+
**Category:** content
|
|
4
|
+
**Purpose:** Editorial promotional callout — a title, a body paragraph, and an optional image — driven entirely by CMS content. It has no props for its copy: every field arrives through the compiler-injected `$content` prop, backed by the `promotion` content model declared next to the component.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
The image is the source of truth for layout. A Promotion block is present when you see:
|
|
11
|
+
|
|
12
|
+
- An **editorial callout** with a short heading and a supporting sentence — e.g. *"Frete grátis acima de R$ 199"*, *"Compre 3, leve 2"*.
|
|
13
|
+
- Optionally a **banner-style image** above or beside the text.
|
|
14
|
+
- Copy that is **campaign-like and merchant-authored**, not derived from a product offer (no prices, no installments).
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## When to use
|
|
19
|
+
|
|
20
|
+
- Editorial campaign callouts on any page — home, PLP, PDP, cart.
|
|
21
|
+
- Any block whose copy is owned by content editors in the CMS rather than by the page schema.
|
|
22
|
+
|
|
23
|
+
## When NOT to use
|
|
24
|
+
|
|
25
|
+
- Offer-derived promotion text (e.g. *"Compre 3 leve 2"* coming from the product's offer data) → use `PricingPromotions` + `PricingPromotion`.
|
|
26
|
+
- Discount highlight pills (*"Black Friday"*, *"Exclusivo"*) on a price block → use `PricingHighlights` + `PricingHighlight`.
|
|
27
|
+
- A plain static heading or paragraph authored in the schema → use `Text`.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Composition
|
|
32
|
+
|
|
33
|
+
`Promotion` is a standalone leaf — no sub-components, no children.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Promotion
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
It renders a `<section data-fs-promotion>` containing, in order: the optional image (the `Image` particle, layered with `data-fs-promotion-image`), an `<h2 data-fs-text data-fs-text-variant="heading" data-fs-promotion-title>`, and a `<p data-fs-promotion-body>`.
|
|
40
|
+
|
|
41
|
+
**Content, not props.** The title, body, image, and image alt come from a CMS entry of the `promotion` content type, injected as `$content` by the compiler from the element's content locator. When no content is available the component renders `null` (self-hide) — the page omits only this block. Non-compiler hosts (Storybook, tests) pass a typed `$content` fixture directly.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Components
|
|
46
|
+
|
|
47
|
+
### Promotion
|
|
48
|
+
|
|
49
|
+
No schema props.
|
|
50
|
+
|
|
51
|
+
Content model (`promotion`) fields, edited in the CMS:
|
|
52
|
+
|
|
53
|
+
| Field | Type | Required | Notes |
|
|
54
|
+
|-------|------|----------|-------|
|
|
55
|
+
| `title` | string | yes | The heading |
|
|
56
|
+
| `body` | string | yes | Supporting paragraph |
|
|
57
|
+
| `image` | string (uri) | no | Picked via the media gallery widget |
|
|
58
|
+
| `imageAlt` | string | no | Editorial alt text; empty means decorative (`alt=""`) |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Canonical schema example
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"promo": {
|
|
67
|
+
"type": "Promotion",
|
|
68
|
+
"style": ["flex", "flex-col", "gap-[8px]", "w-full"]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Common mistakes
|
|
76
|
+
|
|
77
|
+
- **Passing `title` / `body` / `image` as props.** Promotion has no content props — copy lives in the CMS entry, not in the schema.
|
|
78
|
+
- **Using `Promotion` for offer-derived deals.** Offer promotions come from product data; use `PricingPromotions` + `PricingPromotion`.
|
|
79
|
+
- **Giving `Promotion` children.** It is a leaf; it renders its own image/title/body.
|
|
80
|
+
- **Expecting a placeholder without content.** Without `$content` it renders nothing at all.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Styling
|
|
85
|
+
|
|
86
|
+
`Promotion` renders one DOM container you style directly; the inner nodes carry `data-fs-promotion-*` attributes (and the title inherits the `Text` identity via `data-fs-text`) for CSS targeting.
|
|
87
|
+
|
|
88
|
+
| Component | What you style | Plausible classes |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `Promotion` | The callout container — stacks image, title, and body | `flex flex-col gap-[8px] p-[16px] rounded-[8px] bg-[#F5F5F5] w-full` |
|
|
91
|
+
|
|
92
|
+
Styled example:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"promo": {
|
|
97
|
+
"type": "Promotion",
|
|
98
|
+
"style": ["flex", "flex-col", "gap-[8px]", "p-[16px]", "rounded-[8px]", "bg-[#F5F5F5]", "w-full"]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Related
|
|
106
|
+
|
|
107
|
+
- [[pricing]] — `PricingPromotions` / `PricingPromotion` render offer-derived promotion text; Promotion is CMS-authored and product-independent.
|
|
108
|
+
- [[text]] — plain schema-authored typography; the Promotion title layers the `Text` identity so project typography cascades.
|
|
109
|
+
- [[image]] — the particle Promotion uses for its optional image.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Region
|
|
2
|
+
|
|
3
|
+
**Category:** content
|
|
4
|
+
**Purpose:** The header's region control. `RegionRoot` reads the shopper's active postal code and the city it resolves to, and broadcasts them to its subtree. `RegionTrigger` opens a modal where the shopper types a postal code; `RegionValue` echoes the active one back in the header. The write goes through the `region` action namespace, and the display updates from the cache — never from local state.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
Look in the **top bar or utility strip of the header**, usually beside the account and cart controls:
|
|
11
|
+
|
|
12
|
+
- A **pin or location glyph** followed by either a prompt like "Informe seu CEP" / "Enter your zip code" (no region set) or a **city and postal code** such as "São Paulo, 01310-100" (region set).
|
|
13
|
+
- Tapping it opens a **small centered modal** with one masked input, a submit button, and often a secondary link reading "I don't know my postal code".
|
|
14
|
+
- On a first visit the control reads as a **call to action**; on a return visit it reads as **a value the shopper can correct**. Both are the same component in two states.
|
|
15
|
+
|
|
16
|
+
A city shown without any postal code is not this component — that is usually a store-locator or a delivery-promise widget.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## When to use
|
|
21
|
+
|
|
22
|
+
- In the **shared header layout**, once per storefront, so the region reads the same on every page.
|
|
23
|
+
- Whenever the design shows a persistent postal code the shopper can edit from the header.
|
|
24
|
+
|
|
25
|
+
## When NOT to use
|
|
26
|
+
|
|
27
|
+
- **For the delivery address on checkout** — that is `ShippingAddress`, which captures a full address, not a region.
|
|
28
|
+
- **For a shipping-cost estimate on a product page** — that is `ShippingSimulator`.
|
|
29
|
+
- **As a bare masked input** — that is the `PostalCodeField` particle, which this component composes. Use the particle directly when a host owns the submit.
|
|
30
|
+
- More than once per page: two `RegionRoot` elements mean two modals and two drafts over the same server value.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Composition
|
|
35
|
+
|
|
36
|
+
`RegionRoot` provides the `'region'` context (ADR-0009): it declares `providesContext: 'region'`, and every other part declares `requiresContext: 'region'`, so the schema compiler's `validateContextConstraints` rejects — before emitting any file — a `RegionValue` with no `RegionRoot` ancestor. `useRegion()` throws at render time for the same mistake off the compiled path.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
RegionRoot (header layout; provides 'region' context)
|
|
40
|
+
├── RegionLoading (renders its skeleton child only while unresolved)
|
|
41
|
+
│ └── <skeleton placeholder>
|
|
42
|
+
├── RegionTrigger (opens the modal)
|
|
43
|
+
│ └── RegionValue (the active city + postal code, once set)
|
|
44
|
+
└── RegionModalController (makes the DialogRoot below controlled)
|
|
45
|
+
└── DialogRoot
|
|
46
|
+
└── DialogPortal
|
|
47
|
+
├── DialogBackdrop
|
|
48
|
+
└── DialogViewport
|
|
49
|
+
└── DialogPopup
|
|
50
|
+
├── DialogTitle (names the dialog)
|
|
51
|
+
├── DialogDescription (describes it)
|
|
52
|
+
├── RegionForm (so Enter in the field submits)
|
|
53
|
+
│ ├── RegionPostalCodeControl
|
|
54
|
+
│ └── RegionSubmit
|
|
55
|
+
├── RegionError
|
|
56
|
+
├── RegionUnknownLink
|
|
57
|
+
└── DialogClose
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Every part is independently placeable and **self-gates on region state** — none takes a visibility prop. `RegionValue` renders nothing until a region resolves, `RegionLoading` renders only while it has not, `RegionError` renders only after a refused write, and `RegionUnknownLink` renders only once a destination is set.
|
|
61
|
+
|
|
62
|
+
The `DialogRoot` under `RegionModalController` must **not** carry raw `open` / `onOpenChange`: the controller supplies both, which is what lets the trigger open the modal and a successful write close it.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Components
|
|
67
|
+
|
|
68
|
+
### RegionRoot
|
|
69
|
+
|
|
70
|
+
Root region provider. Reads the resolved `Region` slice reactively and broadcasts `{ postalCode, city, loading, draft, setDraft, pending, errors, open, setOpen, submit }` through `useRegion()`. Owns the modal's draft, the in-flight flag, and the latest write's errors. No props.
|
|
71
|
+
|
|
72
|
+
**Data:** declares `fragment RegionData on Query` (spreads `RegionFragment`: `id`, `postalCode`, `city`) with `fetchStrategy: 'reactive'`. `Query.region` takes no arguments, so the fragment composes into the layout query with no variable to wire.
|
|
73
|
+
|
|
74
|
+
**Write:** declares `actions: { region: ['setPostalCode'] }`. A successful write updates the display purely by the mutation payload normalizing into the Apollo cache — the root never copies the resolved value into state, so a refused write needs no rollback and can never leave the header showing a postal code the server rejected.
|
|
75
|
+
|
|
76
|
+
### RegionTrigger
|
|
77
|
+
|
|
78
|
+
Opens the modal. Shows its unset label while no region is set; compose `RegionValue` as its child so the active region reads inside the control once one is. Not interactive while the region is unresolved.
|
|
79
|
+
|
|
80
|
+
Its accessible name carries the active region, so a screen reader announces where the shopper is shopping rather than a label that reads the same in every state. An authored `aria-label` **prefixes** the visible text rather than replacing it — `"Alterar região: São Paulo 01310-100"`, and `"Alterar região: Informe seu CEP"` while unset — because WCAG 2.5.3 requires the accessible name to contain the visible label, and a name a voice-control user cannot read off the screen is one they cannot speak. Author the verb alone (`"Alterar região"`); the component appends the state.
|
|
81
|
+
|
|
82
|
+
| Prop | Type | Default | Notes |
|
|
83
|
+
|------|------|---------|-------|
|
|
84
|
+
| `unsetLabel` | string | `"Informe seu CEP"` | Shown while no region is set |
|
|
85
|
+
|
|
86
|
+
**Content model** (`region-trigger`): `unsetLabel`, `ariaLabel`.
|
|
87
|
+
|
|
88
|
+
### RegionValue
|
|
89
|
+
|
|
90
|
+
The active region, as city plus postal code. Renders nothing while the region is unresolved or unset. The city sits in its own element (`data-fs-region-value-city`) so a store can style or hide it; when the contract resolves no city, the postal code renders alone with no separator. No props.
|
|
91
|
+
|
|
92
|
+
A plain space separates the two values in the text itself, so the reading is `São Paulo 01310-100` — that run is the trigger's accessible name whenever no `aria-label` is authored, and CSS cannot put a separator into an accessible name. A flex container strips it as leading whitespace, so a store's `gap` still governs the visible spacing.
|
|
93
|
+
|
|
94
|
+
### RegionLoading
|
|
95
|
+
|
|
96
|
+
Renders its children only while the region read has not resolved. Supply a skeleton placeholder as its child, sized like the resolved display so the header does not reflow when the region arrives. No props.
|
|
97
|
+
|
|
98
|
+
### RegionModalController
|
|
99
|
+
|
|
100
|
+
Wraps a single `DialogRoot` child and makes it controlled by the region state: the trigger opens it, Escape and the backdrop close it, and a successful write closes it. A refused write always leaves it open, so the message has somewhere to show. Closing clears the draft and the error. No props.
|
|
101
|
+
|
|
102
|
+
### RegionForm
|
|
103
|
+
|
|
104
|
+
Form wrapper holding `RegionPostalCodeControl` and `RegionSubmit`, so pressing **Enter** in the field submits — the same role `ShippingSimulatorForm` plays for the shipping calculator. Compose it whenever both parts sit together. Without it the shopper has to reach the button, which works but is not what a one-field form leads them to expect.
|
|
105
|
+
|
|
106
|
+
Inside it `RegionSubmit` becomes the form's submit button; on its own it dispatches directly. That is what keeps the form optional instead of load-bearing. Takes all `form` props except `onSubmit`, which it owns.
|
|
107
|
+
|
|
108
|
+
### RegionPostalCodeControl
|
|
109
|
+
|
|
110
|
+
The postal-code input inside the modal: a `PostalCodeField` bound to the region draft, seeded from the persisted value **every time the modal opens**, so a reopened modal shows the truth rather than an abandoned edit. Server rejections mark the field invalid; their message is `RegionError`'s to show, not repeated here. The first keystroke after a rejection clears it, which restores this field's own incomplete-value copy.
|
|
111
|
+
|
|
112
|
+
A refusal also takes focus back to the field, so the shopper lands on the value they have to correct. Without it they land nowhere: the write disables the input, which blurs it, and the document body takes focus — inside an open dialog that means Enter does nothing and Tab restarts from the top.
|
|
113
|
+
|
|
114
|
+
| Prop | Type | Default | Notes |
|
|
115
|
+
|------|------|---------|-------|
|
|
116
|
+
| `mask` | string | `"99999-999"` | Country format, set at build time (default BR CEP) |
|
|
117
|
+
| `label` | string | — | Field label |
|
|
118
|
+
| `placeholder` | string | — | Field placeholder |
|
|
119
|
+
| `errorMessage` | string | — | The field's own incomplete-value message |
|
|
120
|
+
|
|
121
|
+
### RegionSubmit
|
|
122
|
+
|
|
123
|
+
Submits the draft. Inert while the draft is empty and while a write is in flight, so a double activation dispatches exactly one mutation. The pending state is exposed as `aria-busy` with no built-in spinner. The value is passed to the server exactly as the shopper typed it, mask included — the server is authoritative on format.
|
|
124
|
+
|
|
125
|
+
| Prop | Type | Default | Notes |
|
|
126
|
+
|------|------|---------|-------|
|
|
127
|
+
| `label` | string | `"Aplicar"` | Button label |
|
|
128
|
+
| `pendingLabel` | string | `"Aplicando..."` | Shown while the write is in flight |
|
|
129
|
+
|
|
130
|
+
**Content model** (`region-submit`): `label`, `pendingLabel`.
|
|
131
|
+
|
|
132
|
+
### RegionError
|
|
133
|
+
|
|
134
|
+
Shows why the latest write was refused, announced as an alert when it appears. Renders nothing when there is no error. A rejection the server codes (`INVALID_POSTAL_CODE`, `UNSERVED_POSTAL_CODE`) and a transport failure (`NETWORK_ERROR`) render the message catalog's copy in the store's language; the server's own English message is shown only for a code the catalog does not know. The authored generic message covers a transport failure, where there is no server message to show, and wins over the catalog there. No props.
|
|
135
|
+
|
|
136
|
+
**Content model** (`region-error`): `genericMessage`.
|
|
137
|
+
|
|
138
|
+
### RegionUnknownLink
|
|
139
|
+
|
|
140
|
+
The "I don't know my postal code" escape hatch, pointing at a postal-code lookup service. Renders nothing until a destination is set, so a store without one composes it and simply gets no link rather than a dead one. The destination passes the same allowlist the link particles use, so a `javascript:` value is refused rather than rendered.
|
|
141
|
+
|
|
142
|
+
A lookup service is a natural new-tab destination, so a store may compose this with `target="_blank"`; the component then adds `noopener` and `noreferrer` to `rel` itself, the same contract `NavigationLink` states. Do not pass `rel` for that reason.
|
|
143
|
+
|
|
144
|
+
| Prop | Type | Default | Notes |
|
|
145
|
+
|------|------|---------|-------|
|
|
146
|
+
| `href` | string | — | Lookup service URL; authored content wins over it |
|
|
147
|
+
| `target` | `"_self"` \| `"_blank"` | `"_self"` | `_blank` adds the tab-nabbing defense |
|
|
148
|
+
|
|
149
|
+
Link text comes from authored content, else from `children`.
|
|
150
|
+
|
|
151
|
+
**Content model** (`region-unknown-link`): `text`, `href`.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Canonical schema example
|
|
156
|
+
|
|
157
|
+
Header region control with the full modal:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"region": { "type": "RegionRoot", "children": ["region-trigger", "region-modal"] },
|
|
162
|
+
"region-trigger": { "type": "RegionTrigger", "children": ["region-value"] },
|
|
163
|
+
"region-value": { "type": "RegionValue" },
|
|
164
|
+
"region-modal": { "type": "RegionModalController", "children": ["region-dialog"] },
|
|
165
|
+
"region-dialog": { "type": "DialogRoot", "children": ["region-portal"] },
|
|
166
|
+
"region-portal": { "type": "DialogPortal", "children": ["region-backdrop", "region-viewport"] },
|
|
167
|
+
"region-backdrop": { "type": "DialogBackdrop" },
|
|
168
|
+
"region-viewport": { "type": "DialogViewport", "children": ["region-popup"] },
|
|
169
|
+
"region-popup": {
|
|
170
|
+
"type": "DialogPopup",
|
|
171
|
+
"children": ["region-title", "region-form", "region-error", "region-unknown"]
|
|
172
|
+
},
|
|
173
|
+
"region-title": { "type": "DialogTitle", "props": { "children": "Escolha sua região" } },
|
|
174
|
+
"region-form": { "type": "RegionForm", "children": ["region-input", "region-submit"] },
|
|
175
|
+
"region-input": { "type": "RegionPostalCodeControl", "props": { "label": "CEP" } },
|
|
176
|
+
"region-submit": { "type": "RegionSubmit" },
|
|
177
|
+
"region-error": { "type": "RegionError" },
|
|
178
|
+
"region-unknown": { "type": "RegionUnknownLink" }
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Styling hooks
|
|
185
|
+
|
|
186
|
+
| Attribute | On | Present when |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `data-fs-region-root` | `RegionRoot` | Always |
|
|
189
|
+
| `data-fs-region-trigger` | `RegionTrigger` | Always |
|
|
190
|
+
| `data-fs-region-trigger-unset` | `RegionTrigger` | No region is set |
|
|
191
|
+
| `data-fs-region-value` | `RegionValue` | A region is set |
|
|
192
|
+
| `data-fs-region-value-city` | The city element | The contract resolved a city |
|
|
193
|
+
| `data-fs-region-loading` | `RegionLoading` | The region is unresolved |
|
|
194
|
+
| `data-fs-region-form` | `RegionForm` | Always |
|
|
195
|
+
| `data-fs-region-postal-code-control` | `RegionPostalCodeControl` | Always |
|
|
196
|
+
| `data-fs-region-submit` | `RegionSubmit` | Always |
|
|
197
|
+
| `data-fs-region-submit-pending` | `RegionSubmit` | A write is in flight |
|
|
198
|
+
| `data-fs-region-error` | `RegionError` | The latest write was refused |
|
|
199
|
+
| `data-fs-region-unknown-link` | `RegionUnknownLink` | A destination is authored |
|
|
200
|
+
|
|
201
|
+
No part ships visual CSS of its own; the visible spacing between city and postal code, the modal's box, and the skeleton's shape are all swatch decisions.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Common mistakes
|
|
206
|
+
|
|
207
|
+
- **Inventing `Region` (without `Root`).** The provider is `RegionRoot`.
|
|
208
|
+
- **Setting `open` or `onOpenChange` on the `DialogRoot` inside `RegionModalController`.** The controller owns both; a raw value fights it and the modal stops responding to the trigger.
|
|
209
|
+
- **Placing `RegionPostalCodeControl`, `RegionSubmit`, or `RegionError` outside the dialog.** They render fine but the shopper meets a postal-code form sitting in the header.
|
|
210
|
+
- **Omitting `RegionForm`.** The parts still work and the button is still keyboard-reachable, but Enter in the field does nothing — the one thing a shopper typing a postal code expects.
|
|
211
|
+
- **Authoring the state into `RegionTrigger`'s `aria-label`** (`"Alterar região: São Paulo"`). The component appends the state; author only the verb, or the name repeats itself.
|
|
212
|
+
- **Looking for a `showCity` or `hideWhenEmpty` prop.** There is none: `RegionValue` self-gates on region state, and a store that shows only the postal code styles the city element away.
|
|
213
|
+
- **Expecting `RegionValue` to show the value the shopper just typed.** It shows what the server confirmed. The draft is the field's, and it is discarded when the modal closes.
|
|
214
|
+
- **Stripping the mask before submitting.** Pass the value through; the server normalizes it and echoes back a formatted one.
|
|
215
|
+
- **Authoring link text but no destination on `RegionUnknownLink`.** Without a destination the link is omitted entirely.
|
|
216
|
+
- **Passing a static `errorMessage` to `RegionPostalCodeControl` and expecting it on a refused write.** That copy describes an incomplete value and is withheld while a server rejection stands, so `RegionError` carries the reason alone.
|
|
217
|
+
- **Using two `RegionRoot` elements.** One per storefront, in the shared header layout.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Related
|
|
222
|
+
|
|
223
|
+
- [[../../particles/postal-code-field/postal-code-field]] — the masked input this component composes; use it directly when a host owns the submit.
|
|
224
|
+
- [[shipping-address]] — the full delivery address on checkout, and the closest structural sibling (reactive root, draft-bearing context, overlay controller, submit through `$actions`).
|
|
225
|
+
- [[session]] — the other read-oriented header-adjacent provider; same ADR-0009 provider/consumer contract and the same Symbol-guarded hook shape.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Session
|
|
2
|
+
|
|
3
|
+
**Category:** identity
|
|
4
|
+
**Purpose:** Read-only identity-state provider, normally hosted by the shared layout. `SessionRoot` reads the shopper's `Session`, derives the **anonymous / identified / logged-in** tri-state, and broadcasts it (plus the raw `loggedIn` and `canEditData` flags) to its subtree. `SessionGuard` is a declarative gate that shows or hides a subtree by that state. It carries session state only — profile fields (name, email, document, phone) live on other components and are read separately.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
`SessionRoot` is a data provider, not a visible widget — you do not see it directly. The image is the source of truth for layout. What signals a session-aware page is content that **changes by identity state**:
|
|
11
|
+
|
|
12
|
+
- A **"welcome back" / identified-shopper** greeting versus a generic **login prompt** for anonymous visitors.
|
|
13
|
+
- An identity step that shows **editable fields** for a logged-in shopper but **masked / read-only** values for an identified guest (this maps to `canEditData`).
|
|
14
|
+
- A subtree that is **only shown once the shopper is identified or logged in** (e.g. saved addresses, order history entry points).
|
|
15
|
+
|
|
16
|
+
When the image shows identity-conditional content — on the identity step or in the header — that tree is hosted under a `SessionRoot`, and the conditional subtrees are gated with `SessionGuard`.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## When to use
|
|
21
|
+
|
|
22
|
+
- As the **root of the shared layout**, wrapping the header subtree and the `Slot`, when identity state is read shell-wide — an account entry point in the header, identity-conditional content on any page. This is the default: like `CartRoot`, `SessionRoot` is shell-wide.
|
|
23
|
+
- As the **page root**, only when exactly one page reads session state and no layout element does.
|
|
24
|
+
- Wrap any subtree that should appear only in certain states in a `SessionGuard` inside that `SessionRoot`.
|
|
25
|
+
|
|
26
|
+
Exactly one `SessionRoot` per rendered tree. A layout-hosted provider already covers every page it serves, so those pages must not add their own.
|
|
27
|
+
|
|
28
|
+
## When NOT to use
|
|
29
|
+
|
|
30
|
+
- **On a page whose layout already hosts one** — the nested provider makes the inner root re-fetch and shadow the outer context. Delete the page-level element instead.
|
|
31
|
+
- **For profile field values** (name, email, document, phone) — those are separate components, not derived from session state.
|
|
32
|
+
- As a generic layout wrapper — that is `Container`.
|
|
33
|
+
|
|
34
|
+
## Layout hosting and the server-side fetch
|
|
35
|
+
|
|
36
|
+
Hosting `SessionRoot` in a layout is safe, and safer than page hosting. The two scopes fetch differently:
|
|
37
|
+
|
|
38
|
+
- **Layout scope** emits `LAYOUT_QUERY` inside a client component with no SSR seed — no `serverVariables`, no `initialData`. The read happens in the browser, where the shopper's cookie is present.
|
|
39
|
+
- **Page scope** emits a server-side `fetchPageData` and seeds the reactive root with `initialData`. That server fetch carries no user cookie, so the seed is an anonymous session — and the root then reads `cache-first` against the cache it just seeded, so that anonymous session is what the page keeps. It does not correct itself.
|
|
40
|
+
|
|
41
|
+
So the cookie hazard belongs to page hosting, not layout hosting. Cost is per layout, not per element: `LAYOUT_QUERY` is composed from the component types used in the layout, with no notion of tree position, so the provider's depth does not change what is fetched.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Composition
|
|
46
|
+
|
|
47
|
+
`SessionRoot` provides the `'session'` context (ADR-0009): it declares `providesContext: 'session'`, and downstream session consumers declare `requiresContext: 'session'`, so the schema compiler's `validateContextConstraints` rejects — before emitting any file — any session consumer that has no `SessionRoot` ancestor. The ancestor may live in the page or in the layout that hosts it: the compiler composes the layout's provider set with the page's before validating, so a layout-hosted `SessionRoot` satisfies consumers on every page it serves. `SessionGuard` reads that context via `useSession()` and throws at render time if placed outside a `SessionRoot`.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
SessionRoot (layout or page root; provides 'session' context)
|
|
51
|
+
├── SessionGuard (when=[...]) (renders children only in the listed states)
|
|
52
|
+
│ └── <identity subtree>
|
|
53
|
+
└── <identity components> (read session state directly)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`SessionGuard` elements are siblings/children inside the `SessionRoot` `children` array; child order reflects visual order. A `SessionGuard` renders its children only when the current state is one of its `when` values and renders nothing otherwise (including when `when` is omitted).
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Components
|
|
61
|
+
|
|
62
|
+
### SessionRoot
|
|
63
|
+
|
|
64
|
+
Root identity-state provider. Reads the resolved `Session` slice reactively and broadcasts a memoized `{ state, loggedIn, canEditData, loading }` to descendants via `useSession()`. Derives `state` from the raw flags: `loggedIn` wins, else `isIdentified` → `identified`, else `anonymous`. While the entity is unresolved it broadcasts `loading: true` and `state: 'anonymous'` (the least-privileged default). No props.
|
|
65
|
+
|
|
66
|
+
**Data:** declares `fragment SessionData on Query` (spreads `SessionFragment`: `id`, `loggedIn`, `canEditData`, `isIdentified`) with `fetchStrategy: 'reactive'` — cache writes (e.g. a post-login refresh) re-render descendants without a refetch. The compiler injects the resolved slice; no explicit data wiring needed.
|
|
67
|
+
|
|
68
|
+
### SessionGuard
|
|
69
|
+
|
|
70
|
+
Declarative state gate. Renders its children only when the current session state is one of the values in `when`; renders nothing for any other state, and nothing when `when` is omitted. Must be inside `SessionRoot`.
|
|
71
|
+
|
|
72
|
+
| Prop | Type | Default | Notes |
|
|
73
|
+
|------|------|---------|-------|
|
|
74
|
+
| `when` | array of `"anonymous"` \| `"identified"` \| `"loggedIn"` | — | The states under which the children render (e.g. `["identified", "loggedIn"]`) |
|
|
75
|
+
|
|
76
|
+
**A guard shows the anonymous branch until the layout read resolves.** `SessionGuard` gates on `state` alone, and `state` is `anonymous` while `loading` is true — so under a layout-hosted provider, whose read is client-only with no SSR seed, every page load starts with the anonymous branch and swaps once the session arrives. Reveal-once-identified subtrees are fine with that; a control whose wrong state actively misleads the shopper is not, and belongs in a component that reads `loading` itself rather than behind a guard.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Canonical schema example
|
|
81
|
+
|
|
82
|
+
Shared layout with `SessionRoot` as the root, above both the header and the `Slot`, and a `SessionGuard` revealing an order-history entry point once the shopper is identified:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"name": "default",
|
|
87
|
+
"root": "layout-session",
|
|
88
|
+
"elements": {
|
|
89
|
+
"layout-session": { "type": "SessionRoot", "children": ["layout-root"] },
|
|
90
|
+
"layout-root": { "type": "Container", "children": ["header", "slot"] },
|
|
91
|
+
"header": { "type": "Container", "children": ["orders-block"] },
|
|
92
|
+
"orders-block": {
|
|
93
|
+
"type": "SessionGuard",
|
|
94
|
+
"props": { "when": ["identified", "loggedIn"] },
|
|
95
|
+
"children": ["orders-link"]
|
|
96
|
+
},
|
|
97
|
+
"orders-link": { "type": "NavigationLink", "props": { "href": "/orders", "children": "Meus pedidos" } },
|
|
98
|
+
"slot": { "type": "Slot", "props": {}, "children": [] }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Pages served by this layout add no `SessionRoot` of their own; their session consumers resolve the layout's. Note that `SessionRoot` carries no `style` — it is not a box, so visual framing belongs on the `Container` beneath it.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Common mistakes
|
|
108
|
+
|
|
109
|
+
- **Inventing `Session` (without `Root`).** The provider is `SessionRoot`.
|
|
110
|
+
- **Avoiding the shared layout out of a cookie concern.** Layout scope is client-only and has the cookie; it is page scope that fetches server-side without one.
|
|
111
|
+
- **Keeping a page-level `SessionRoot` when the layout already hosts one.** That nests two reactive roots — the inner one re-fetches and shadows the outer context.
|
|
112
|
+
- **Placing `SessionRoot` beside the `Slot` instead of above it.** A sibling provider still serves the consumers in its own branch, so the header keeps working while every consumer under the `Slot` fails; it must be an ancestor of both the header subtree and the `Slot`.
|
|
113
|
+
- **Placing `SessionGuard` outside a `SessionRoot`.** It reads `useSession()` and throws at render time; the schema compiler also rejects it for missing the `'session'` context ancestor.
|
|
114
|
+
- **Omitting `when` and expecting children to show.** With no `when`, `SessionGuard` renders nothing — pass the states that should render.
|
|
115
|
+
- **Gating the header's account entry point with a pair of guards.** That is `AccountEntryPoint`, which reads the session itself and renders a resolving state; guards would hide that state and flash the wrong label. See [[account-entry-point]].
|
|
116
|
+
- **Reading profile fields (name, email, document, phone) from `SessionRoot`.** It broadcasts session state only; profile values come from separate identity components.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Related
|
|
121
|
+
|
|
122
|
+
- [[cart]] — the other reactive data provider, hosted in the shared layout the same way and for the same reason.
|
|
123
|
+
- [[product-information]] — another context provider (`providesContext: 'product-information'`); same ADR-0009 provider/consumer contract, different domain.
|