create-tanqory-theme 0.0.0-stage → 0.2.2
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/LICENSE +21 -0
- package/README.md +55 -2
- package/bin/create-tanqory-theme.mjs +13 -0
- package/lib/agents.mjs +118 -0
- package/lib/create.mjs +245 -0
- package/package.json +39 -4
- package/templates/blank/README.md +13 -0
- package/templates/blank/config/settings.json +3 -0
- package/templates/blank/locales/en.json +22 -0
- package/templates/blank/locales/th.json +22 -0
- package/templates/blank/package.json +34 -0
- package/templates/blank/regions/footer.json +35 -0
- package/templates/blank/regions/header.json +13 -0
- package/templates/blank/scripts/build.mjs +193 -0
- package/templates/blank/scripts/probe.ts +4 -0
- package/templates/blank/src/bundle.ts +15 -0
- package/templates/blank/src/components/parts.tsx +32 -0
- package/templates/blank/src/entry.client.tsx +6 -0
- package/templates/blank/src/entry.server.tsx +13 -0
- package/templates/blank/src/sections/chrome.tsx +132 -0
- package/templates/blank/src/sections/commerce.tsx +242 -0
- package/templates/blank/src/sections/content.tsx +111 -0
- package/templates/blank/src/sections/editorial.tsx +107 -0
- package/templates/blank/src/settings.ts +15 -0
- package/templates/blank/src/styles/theme.css +195 -0
- package/templates/blank/src/templates.ts +14 -0
- package/templates/blank/src/theme.tsx +76 -0
- package/templates/blank/template.json +24 -0
- package/templates/blank/templates/404.json +14 -0
- package/templates/blank/templates/account.json +14 -0
- package/templates/blank/templates/article.json +14 -0
- package/templates/blank/templates/blog.json +14 -0
- package/templates/blank/templates/cart.json +14 -0
- package/templates/blank/templates/collection.json +14 -0
- package/templates/blank/templates/index.json +36 -0
- package/templates/blank/templates/page.contact.json +18 -0
- package/templates/blank/templates/page.json +14 -0
- package/templates/blank/templates/product.json +18 -0
- package/templates/blank/templates/search.json +14 -0
- package/templates/blank/tests/theme.test.ts +49 -0
- package/templates/blank/tsconfig.json +17 -0
- package/templates/blank/vite.client.config.ts +17 -0
- package/templates/blank/vite.probe.config.ts +16 -0
- package/templates/blank/vite.server.config.ts +32 -0
- package/templates/blank/vitest.config.ts +9 -0
- package/templates/common/.claude/settings.json +8 -0
- package/templates/common/.codex/config.toml +4 -0
- package/templates/common/.gemini/settings.json +12 -0
- package/templates/common/.github/workflows/theme-check.yml +44 -0
- package/templates/common/.mcp.json +12 -0
- package/templates/common/.vscode/mcp.json +9 -0
- package/templates/common/_gitignore +9 -0
- package/templates/common/scripts/check.mjs +44 -0
- package/templates/common/scripts/dev.mjs +30 -0
- package/templates/common/tanqory.config.json +6 -0
- package/templates/nova/.gitattributes +3 -0
- package/templates/nova/.tanqory/contracts/LOCK.json +66 -0
- package/templates/nova/.tanqory/contracts/nova-01.json +1092 -0
- package/templates/nova/.tanqory/contracts/nova-12A.json +2674 -0
- package/templates/nova/.tanqory/contracts/nova-12B.json +2285 -0
- package/templates/nova/.tanqory/contracts/nova-12C.json +2848 -0
- package/templates/nova/.tanqory/contracts/nova-12D.json +3039 -0
- package/templates/nova/.tanqory/contracts/nova-12E.json +1473 -0
- package/templates/nova/.tanqory/contracts/nova-12F.json +1670 -0
- package/templates/nova/.tanqory/contracts/nova-12G.json +1661 -0
- package/templates/nova/.tanqory/contracts/nova-13.json +301 -0
- package/templates/nova/.tanqory/contracts/nova-14.json +172 -0
- package/templates/nova/.tanqory/legacy-settings.json +1121 -0
- package/templates/nova/README.md +150 -0
- package/templates/nova/_gitignore +28 -0
- package/templates/nova/assets/ai-tokens.css +6 -0
- package/templates/nova/assets/starter/starter-art-1200x800-0b773ac4.svg +1 -0
- package/templates/nova/assets/starter/starter-art-1200x900-59600290.svg +1 -0
- package/templates/nova/assets/starter/starter-art-1200x900-c549d496.svg +1 -0
- package/templates/nova/assets/starter/starter-art-1600x900-8f979fcb.svg +1 -0
- package/templates/nova/assets/starter/starter-art-2100x900-cbb71bfd.svg +1 -0
- package/templates/nova/config/legacy-settings.json +438 -0
- package/templates/nova/config/promotion-popup.schema.ts +29 -0
- package/templates/nova/config/settings.json +23 -0
- package/templates/nova/config/settings.schema.ts +231 -0
- package/templates/nova/entry.abi.tsx +27 -0
- package/templates/nova/groups/footer.json +39 -0
- package/templates/nova/groups/header.json +11 -0
- package/templates/nova/lib/abi/abi.css +41 -0
- package/templates/nova/lib/bundle-core.ts +35 -0
- package/templates/nova/lib/bundle.client.ts +15 -0
- package/templates/nova/lib/bundle.ts +15 -0
- package/templates/nova/lib/legacy-settings.ts +104 -0
- package/templates/nova/locales/ar.json +590 -0
- package/templates/nova/locales/en.json +590 -0
- package/templates/nova/locales/th.json +590 -0
- package/templates/nova/main.abi.tsx +12 -0
- package/templates/nova/package.json +44 -0
- package/templates/nova/scripts/abi/build.mjs +145 -0
- package/templates/nova/scripts/abi/check-content.mjs +99 -0
- package/templates/nova/scripts/abi/jcs.mjs +27 -0
- package/templates/nova/scripts/abi/local-host.mjs +294 -0
- package/templates/nova/scripts/abi/manifest.mjs +392 -0
- package/templates/nova/scripts/abi/starter-assets.mjs +32 -0
- package/templates/nova/scripts/compiler/checks/check-a11y.mjs +88 -0
- package/templates/nova/scripts/compiler/checks/check-layout-sanity.mjs +169 -0
- package/templates/nova/scripts/compiler/checks/check-page-headings.mjs +67 -0
- package/templates/nova/scripts/compiler/checks/check-product-jsonld.mjs +80 -0
- package/templates/nova/scripts/compiler/checks/theme-spec/check.mjs +88 -0
- package/templates/nova/scripts/compiler/contracts/extract.mjs +188 -0
- package/templates/nova/scripts/compiler/contracts/index.ts +125 -0
- package/templates/nova/scripts/compiler/contracts/validate.ts +347 -0
- package/templates/nova/scripts/compiler/manifest/gen-manifest.mjs +156 -0
- package/templates/nova/scripts/compiler/manifest/manifest-entry.ts +386 -0
- package/templates/nova/scripts/compiler/manifest/manifest-sections.ts +247 -0
- package/templates/nova/scripts/compiler/migrate/migrate-content.mjs +171 -0
- package/templates/nova/scripts/compiler/migrate/migrate-groups.mjs +159 -0
- package/templates/nova/scripts/parity/case-shim.ts +14 -0
- package/templates/nova/scripts/parity/emit.ts +206 -0
- package/templates/nova/scripts/parity/index.html +12 -0
- package/templates/nova/scripts/parity/main.tsx +23 -0
- package/templates/nova/scripts/parity/pages.tsx +123 -0
- package/templates/nova/scripts/parity/vite.config.ts +29 -0
- package/templates/nova/src/components/base/Accordion.css +20 -0
- package/templates/nova/src/components/base/Accordion.tsx +50 -0
- package/templates/nova/src/components/base/Badge.css +17 -0
- package/templates/nova/src/components/base/Badge.tsx +26 -0
- package/templates/nova/src/components/base/Button.css +73 -0
- package/templates/nova/src/components/base/Button.tsx +64 -0
- package/templates/nova/src/components/base/Checkbox.tsx +18 -0
- package/templates/nova/src/components/base/Chip.css +20 -0
- package/templates/nova/src/components/base/Chip.tsx +33 -0
- package/templates/nova/src/components/base/Divider.css +6 -0
- package/templates/nova/src/components/base/Divider.tsx +28 -0
- package/templates/nova/src/components/base/IconButton.css +34 -0
- package/templates/nova/src/components/base/IconButton.tsx +41 -0
- package/templates/nova/src/components/base/Input.css +21 -0
- package/templates/nova/src/components/base/Input.tsx +24 -0
- package/templates/nova/src/components/base/Link.css +15 -0
- package/templates/nova/src/components/base/Link.tsx +23 -0
- package/templates/nova/src/components/base/Radio.tsx +13 -0
- package/templates/nova/src/components/base/Select.css +18 -0
- package/templates/nova/src/components/base/Select.tsx +34 -0
- package/templates/nova/src/components/base/Skeleton.css +17 -0
- package/templates/nova/src/components/base/Skeleton.tsx +23 -0
- package/templates/nova/src/components/base/Spinner.css +13 -0
- package/templates/nova/src/components/base/Spinner.tsx +8 -0
- package/templates/nova/src/components/base/Switch.tsx +16 -0
- package/templates/nova/src/components/base/Tabs.css +24 -0
- package/templates/nova/src/components/base/Tabs.tsx +61 -0
- package/templates/nova/src/components/base/Textarea.css +1 -0
- package/templates/nova/src/components/base/Textarea.tsx +8 -0
- package/templates/nova/src/components/base/choice-props.ts +9 -0
- package/templates/nova/src/components/base/choice.css +69 -0
- package/templates/nova/src/components/commerce/AddToCart.css +4 -0
- package/templates/nova/src/components/commerce/AddToCart.tsx +77 -0
- package/templates/nova/src/components/commerce/CartLine.css +26 -0
- package/templates/nova/src/components/commerce/CartLine.tsx +82 -0
- package/templates/nova/src/components/commerce/CartSummary.css +23 -0
- package/templates/nova/src/components/commerce/CartSummary.tsx +120 -0
- package/templates/nova/src/components/commerce/CollectionCard.css +66 -0
- package/templates/nova/src/components/commerce/CollectionCard.tsx +57 -0
- package/templates/nova/src/components/commerce/ComparisonTable.css +33 -0
- package/templates/nova/src/components/commerce/ComparisonTable.tsx +113 -0
- package/templates/nova/src/components/commerce/EarnHint.css +3 -0
- package/templates/nova/src/components/commerce/EarnHint.tsx +31 -0
- package/templates/nova/src/components/commerce/Hotspot.css +16 -0
- package/templates/nova/src/components/commerce/Hotspot.tsx +64 -0
- package/templates/nova/src/components/commerce/ListingParts.css +79 -0
- package/templates/nova/src/components/commerce/ListingParts.tsx +211 -0
- package/templates/nova/src/components/commerce/LookItem.css +19 -0
- package/templates/nova/src/components/commerce/LookItem.tsx +41 -0
- package/templates/nova/src/components/commerce/MiniCartLine.css +20 -0
- package/templates/nova/src/components/commerce/MiniCartLine.tsx +93 -0
- package/templates/nova/src/components/commerce/PaymentMark.css +2 -0
- package/templates/nova/src/components/commerce/PaymentMark.tsx +55 -0
- package/templates/nova/src/components/commerce/Price.css +27 -0
- package/templates/nova/src/components/commerce/Price.tsx +55 -0
- package/templates/nova/src/components/commerce/ProductCard.css +66 -0
- package/templates/nova/src/components/commerce/ProductCard.tsx +117 -0
- package/templates/nova/src/components/commerce/ProductGallery.css +72 -0
- package/templates/nova/src/components/commerce/ProductGallery.tsx +98 -0
- package/templates/nova/src/components/commerce/QuantitySelector.css +23 -0
- package/templates/nova/src/components/commerce/QuantitySelector.tsx +45 -0
- package/templates/nova/src/components/commerce/Reviews.css +34 -0
- package/templates/nova/src/components/commerce/Reviews.tsx +112 -0
- package/templates/nova/src/components/commerce/Stars.css +7 -0
- package/templates/nova/src/components/commerce/Stars.tsx +38 -0
- package/templates/nova/src/components/commerce/VariantPicker.css +31 -0
- package/templates/nova/src/components/commerce/VariantPicker.tsx +122 -0
- package/templates/nova/src/components/commerce/blocks/Badge.css +6 -0
- package/templates/nova/src/components/commerce/blocks/Badge.tsx +33 -0
- package/templates/nova/src/components/commerce/blocks/PaymentIcons.tsx +43 -0
- package/templates/nova/src/components/commerce/blocks/Swatches.tsx +17 -0
- package/templates/nova/src/components/commerce/blocks/payment.css +2 -0
- package/templates/nova/src/components/commerce/cart-limit.ts +11 -0
- package/templates/nova/src/components/commerce/money.ts +34 -0
- package/templates/nova/src/components/commerce/swatch.ts +54 -0
- package/templates/nova/src/components/content/ArticleCard.css +22 -0
- package/templates/nova/src/components/content/ArticleCard.tsx +53 -0
- package/templates/nova/src/components/content/BeforeAfterSlider.css +38 -0
- package/templates/nova/src/components/content/BeforeAfterSlider.tsx +87 -0
- package/templates/nova/src/components/content/CardRow.css +80 -0
- package/templates/nova/src/components/content/CardRow.tsx +140 -0
- package/templates/nova/src/components/content/CarouselControls.css +61 -0
- package/templates/nova/src/components/content/CarouselControls.tsx +90 -0
- package/templates/nova/src/components/content/ContentsNav.css +16 -0
- package/templates/nova/src/components/content/ContentsNav.tsx +55 -0
- package/templates/nova/src/components/content/Countdown.css +24 -0
- package/templates/nova/src/components/content/Countdown.tsx +115 -0
- package/templates/nova/src/components/content/HeroCopy.css +92 -0
- package/templates/nova/src/components/content/HeroCopy.tsx +148 -0
- package/templates/nova/src/components/content/HeroFrame.css +75 -0
- package/templates/nova/src/components/content/HeroFrame.tsx +49 -0
- package/templates/nova/src/components/content/HeroMedia.css +40 -0
- package/templates/nova/src/components/content/HeroMedia.tsx +170 -0
- package/templates/nova/src/components/content/LongForm.css +40 -0
- package/templates/nova/src/components/content/LongForm.tsx +56 -0
- package/templates/nova/src/components/content/MediaFrame.css +5 -0
- package/templates/nova/src/components/content/MediaFrame.tsx +52 -0
- package/templates/nova/src/components/content/RichText.tsx +30 -0
- package/templates/nova/src/components/content/SectionHead.css +8 -0
- package/templates/nova/src/components/content/SectionHead.tsx +34 -0
- package/templates/nova/src/components/content/ShareButtons.css +2 -0
- package/templates/nova/src/components/content/ShareButtons.tsx +40 -0
- package/templates/nova/src/components/content/StoreCard.css +14 -0
- package/templates/nova/src/components/content/StoreCard.tsx +60 -0
- package/templates/nova/src/components/content/TestimonialCard.css +36 -0
- package/templates/nova/src/components/content/TestimonialCard.tsx +102 -0
- package/templates/nova/src/components/content/UgcTile.css +8 -0
- package/templates/nova/src/components/content/UgcTile.tsx +33 -0
- package/templates/nova/src/components/content/blocks/Button.tsx +36 -0
- package/templates/nova/src/components/content/blocks/Column.tsx +30 -0
- package/templates/nova/src/components/content/blocks/Heading.tsx +31 -0
- package/templates/nova/src/components/content/blocks/Icon.tsx +25 -0
- package/templates/nova/src/components/content/blocks/Image.tsx +42 -0
- package/templates/nova/src/components/content/blocks/Text.tsx +26 -0
- package/templates/nova/src/components/content/blocks/Video.tsx +44 -0
- package/templates/nova/src/components/content/blocks/content.css +25 -0
- package/templates/nova/src/components/content/blocks/shared.ts +5 -0
- package/templates/nova/src/components/forms/ContactForm.css +19 -0
- package/templates/nova/src/components/forms/ContactForm.tsx +139 -0
- package/templates/nova/src/components/forms/FormField.css +15 -0
- package/templates/nova/src/components/forms/FormField.tsx +77 -0
- package/templates/nova/src/components/forms/GatewayFields.tsx +79 -0
- package/templates/nova/src/components/forms/NewsletterForm.css +9 -0
- package/templates/nova/src/components/forms/NewsletterForm.tsx +141 -0
- package/templates/nova/src/components/forms/SearchField.css +7 -0
- package/templates/nova/src/components/forms/SearchField.tsx +68 -0
- package/templates/nova/src/components/forms/blocks/Newsletter.css +5 -0
- package/templates/nova/src/components/forms/blocks/Newsletter.tsx +71 -0
- package/templates/nova/src/components/forms/validate.ts +4 -0
- package/templates/nova/src/components/listing/ProductListing.tsx +200 -0
- package/templates/nova/src/components/navigation/BrandLogo.tsx +35 -0
- package/templates/nova/src/components/navigation/Breadcrumb.css +16 -0
- package/templates/nova/src/components/navigation/Breadcrumb.tsx +59 -0
- package/templates/nova/src/components/navigation/LineAddFriend.css +5 -0
- package/templates/nova/src/components/navigation/LineAddFriend.tsx +42 -0
- package/templates/nova/src/components/navigation/LocaleSelector.css +33 -0
- package/templates/nova/src/components/navigation/LocaleSelector.tsx +64 -0
- package/templates/nova/src/components/navigation/Menu.tsx +26 -0
- package/templates/nova/src/components/navigation/Pagination.css +24 -0
- package/templates/nova/src/components/navigation/Pagination.tsx +74 -0
- package/templates/nova/src/components/navigation/RecoveryLinks.css +8 -0
- package/templates/nova/src/components/navigation/RecoveryLinks.tsx +23 -0
- package/templates/nova/src/components/navigation/SocialLinks.css +5 -0
- package/templates/nova/src/components/navigation/SocialLinks.tsx +51 -0
- package/templates/nova/src/components/navigation/blocks/SocialLinks.tsx +23 -0
- package/templates/nova/src/components/overlays/AccountMenu.css +11 -0
- package/templates/nova/src/components/overlays/AccountMenu.tsx +91 -0
- package/templates/nova/src/components/overlays/CartDrawer.css +17 -0
- package/templates/nova/src/components/overlays/CartDrawer.tsx +118 -0
- package/templates/nova/src/components/overlays/ConfirmationDialog.tsx +49 -0
- package/templates/nova/src/components/overlays/CookieConsent.css +56 -0
- package/templates/nova/src/components/overlays/CookieConsent.tsx +170 -0
- package/templates/nova/src/components/overlays/Drawer.css +38 -0
- package/templates/nova/src/components/overlays/Drawer.tsx +66 -0
- package/templates/nova/src/components/overlays/FriendOfferPanel.tsx +79 -0
- package/templates/nova/src/components/overlays/MarketSelector.css +111 -0
- package/templates/nova/src/components/overlays/MarketSelector.tsx +450 -0
- package/templates/nova/src/components/overlays/Menu.css +9 -0
- package/templates/nova/src/components/overlays/Menu.tsx +124 -0
- package/templates/nova/src/components/overlays/MobileNavDrawer.css +28 -0
- package/templates/nova/src/components/overlays/MobileNavDrawer.tsx +166 -0
- package/templates/nova/src/components/overlays/Modal.css +52 -0
- package/templates/nova/src/components/overlays/Modal.tsx +88 -0
- package/templates/nova/src/components/overlays/Popover.css +4 -0
- package/templates/nova/src/components/overlays/Popover.tsx +115 -0
- package/templates/nova/src/components/overlays/ProductLightbox.css +20 -0
- package/templates/nova/src/components/overlays/ProductLightbox.tsx +70 -0
- package/templates/nova/src/components/overlays/PromoPopup.css +32 -0
- package/templates/nova/src/components/overlays/PromoPopup.tsx +198 -0
- package/templates/nova/src/components/overlays/QuickView.css +31 -0
- package/templates/nova/src/components/overlays/QuickView.tsx +127 -0
- package/templates/nova/src/components/overlays/ReferralPanel.css +16 -0
- package/templates/nova/src/components/overlays/ReferralPanel.tsx +138 -0
- package/templates/nova/src/components/overlays/RewardsLauncher.css +22 -0
- package/templates/nova/src/components/overlays/RewardsLauncher.tsx +270 -0
- package/templates/nova/src/components/overlays/SearchModal.css +48 -0
- package/templates/nova/src/components/overlays/SearchModal.tsx +302 -0
- package/templates/nova/src/components/overlays/Sheet.css +16 -0
- package/templates/nova/src/components/overlays/Sheet.tsx +48 -0
- package/templates/nova/src/components/overlays/SizeGuide.css +8 -0
- package/templates/nova/src/components/overlays/SizeGuide.tsx +74 -0
- package/templates/nova/src/components/overlays/Toast.css +20 -0
- package/templates/nova/src/components/overlays/Toast.tsx +164 -0
- package/templates/nova/src/components/overlays/Tooltip.css +6 -0
- package/templates/nova/src/components/overlays/Tooltip.tsx +59 -0
- package/templates/nova/src/components/overlays/use-dialog.ts +74 -0
- package/templates/nova/src/components/section/shared.css +42 -0
- package/templates/nova/src/components/section/shared.ts +120 -0
- package/templates/nova/src/components/section/shell.tsx +29 -0
- package/templates/nova/src/components/states/Alert.css +10 -0
- package/templates/nova/src/components/states/Alert.tsx +50 -0
- package/templates/nova/src/components/states/EditorFrame.css +31 -0
- package/templates/nova/src/components/states/EditorFrame.tsx +43 -0
- package/templates/nova/src/components/states/MediaState.css +1 -0
- package/templates/nova/src/components/states/MediaState.tsx +49 -0
- package/templates/nova/src/components/states/OfflineNotice.css +4 -0
- package/templates/nova/src/components/states/OfflineNotice.tsx +24 -0
- package/templates/nova/src/components/states/SkeletonLayout.css +22 -0
- package/templates/nova/src/components/states/SkeletonLayout.tsx +104 -0
- package/templates/nova/src/components/states/StateBlock.css +22 -0
- package/templates/nova/src/components/states/StateBlock.tsx +39 -0
- package/templates/nova/src/components/states/media/MediaError.css +6 -0
- package/templates/nova/src/components/states/media/MediaError.tsx +49 -0
- package/templates/nova/src/components/states/media/MediaPlaceholder.css +24 -0
- package/templates/nova/src/components/states/media/MediaPlaceholder.tsx +101 -0
- package/templates/nova/src/components/states/media/placeholder-art.ts +142 -0
- package/templates/nova/src/layouts/Main.tsx +80 -0
- package/templates/nova/src/sections/Account/Account.tsx +109 -0
- package/templates/nova/src/sections/Account/index.ts +22 -0
- package/templates/nova/src/sections/Account/presets.ts +6 -0
- package/templates/nova/src/sections/Account/schema.ts +29 -0
- package/templates/nova/src/sections/AnnouncementBar/AnnouncementBar.css +37 -0
- package/templates/nova/src/sections/AnnouncementBar/AnnouncementBar.tsx +151 -0
- package/templates/nova/src/sections/AnnouncementBar/index.ts +20 -0
- package/templates/nova/src/sections/AnnouncementBar/presets.ts +10 -0
- package/templates/nova/src/sections/AnnouncementBar/schema.ts +26 -0
- package/templates/nova/src/sections/ArticleBody/ArticleBody.tsx +129 -0
- package/templates/nova/src/sections/ArticleBody/index.ts +21 -0
- package/templates/nova/src/sections/ArticleBody/presets.ts +10 -0
- package/templates/nova/src/sections/ArticleBody/schema.ts +40 -0
- package/templates/nova/src/sections/BeforeAfter/BeforeAfter.tsx +47 -0
- package/templates/nova/src/sections/BeforeAfter/blocks/Comparison.tsx +20 -0
- package/templates/nova/src/sections/BeforeAfter/index.ts +28 -0
- package/templates/nova/src/sections/BeforeAfter/presets.ts +10 -0
- package/templates/nova/src/sections/BeforeAfter/schema.ts +34 -0
- package/templates/nova/src/sections/BlogPosts/BlogPosts.tsx +106 -0
- package/templates/nova/src/sections/BlogPosts/index.ts +30 -0
- package/templates/nova/src/sections/BlogPosts/presets.ts +10 -0
- package/templates/nova/src/sections/BlogPosts/schema.ts +42 -0
- package/templates/nova/src/sections/Breadcrumbs/Breadcrumbs.css +10 -0
- package/templates/nova/src/sections/Breadcrumbs/Breadcrumbs.tsx +41 -0
- package/templates/nova/src/sections/Breadcrumbs/index.ts +20 -0
- package/templates/nova/src/sections/Breadcrumbs/presets.ts +6 -0
- package/templates/nova/src/sections/Breadcrumbs/schema.ts +24 -0
- package/templates/nova/src/sections/CartItems/CartItems.tsx +170 -0
- package/templates/nova/src/sections/CartItems/blocks/CartBadge.tsx +14 -0
- package/templates/nova/src/sections/CartItems/index.ts +37 -0
- package/templates/nova/src/sections/CartItems/presets.ts +8 -0
- package/templates/nova/src/sections/CartItems/schema.ts +34 -0
- package/templates/nova/src/sections/CollectionBanner/CollectionBanner.tsx +120 -0
- package/templates/nova/src/sections/CollectionBanner/index.ts +25 -0
- package/templates/nova/src/sections/CollectionBanner/presets.ts +10 -0
- package/templates/nova/src/sections/CollectionBanner/schema.ts +37 -0
- package/templates/nova/src/sections/CollectionLinks/CollectionLinks.tsx +47 -0
- package/templates/nova/src/sections/CollectionLinks/index.ts +20 -0
- package/templates/nova/src/sections/CollectionLinks/presets.ts +8 -0
- package/templates/nova/src/sections/CollectionLinks/schema.ts +33 -0
- package/templates/nova/src/sections/CollectionList/CollectionList.tsx +89 -0
- package/templates/nova/src/sections/CollectionList/blocks/CollectionItem.tsx +25 -0
- package/templates/nova/src/sections/CollectionList/index.ts +33 -0
- package/templates/nova/src/sections/CollectionList/presets.ts +10 -0
- package/templates/nova/src/sections/CollectionList/schema.ts +41 -0
- package/templates/nova/src/sections/ComparisonTable/ComparisonTable.tsx +52 -0
- package/templates/nova/src/sections/ComparisonTable/blocks/CompareItem.tsx +26 -0
- package/templates/nova/src/sections/ComparisonTable/index.ts +31 -0
- package/templates/nova/src/sections/ComparisonTable/presets.ts +8 -0
- package/templates/nova/src/sections/ComparisonTable/schema.ts +31 -0
- package/templates/nova/src/sections/ContactForm/ContactForm.tsx +83 -0
- package/templates/nova/src/sections/ContactForm/blocks/ContactDetail.tsx +13 -0
- package/templates/nova/src/sections/ContactForm/index.ts +29 -0
- package/templates/nova/src/sections/ContactForm/presets.ts +8 -0
- package/templates/nova/src/sections/ContactForm/schema.ts +34 -0
- package/templates/nova/src/sections/Divider/Divider.css +17 -0
- package/templates/nova/src/sections/Divider/Divider.tsx +21 -0
- package/templates/nova/src/sections/Divider/index.ts +20 -0
- package/templates/nova/src/sections/Divider/presets.ts +10 -0
- package/templates/nova/src/sections/Divider/schema.ts +24 -0
- package/templates/nova/src/sections/Faq/Faq.tsx +51 -0
- package/templates/nova/src/sections/Faq/blocks/FaqItem.tsx +13 -0
- package/templates/nova/src/sections/Faq/index.ts +28 -0
- package/templates/nova/src/sections/Faq/presets.ts +8 -0
- package/templates/nova/src/sections/Faq/schema.ts +32 -0
- package/templates/nova/src/sections/FeatureGridBlocks/FeatureGridBlocks.tsx +90 -0
- package/templates/nova/src/sections/FeatureGridBlocks/index.ts +35 -0
- package/templates/nova/src/sections/FeatureGridBlocks/presets.ts +8 -0
- package/templates/nova/src/sections/FeatureGridBlocks/schema.ts +35 -0
- package/templates/nova/src/sections/FeatureHighlights/FeatureHighlights.tsx +56 -0
- package/templates/nova/src/sections/FeatureHighlights/index.ts +20 -0
- package/templates/nova/src/sections/FeatureHighlights/presets.ts +6 -0
- package/templates/nova/src/sections/FeatureHighlights/schema.ts +27 -0
- package/templates/nova/src/sections/FeaturedCollection/FeaturedCollection.tsx +67 -0
- package/templates/nova/src/sections/FeaturedCollection/index.ts +21 -0
- package/templates/nova/src/sections/FeaturedCollection/presets.ts +10 -0
- package/templates/nova/src/sections/FeaturedCollection/schema.ts +39 -0
- package/templates/nova/src/sections/FeaturedProduct/FeaturedProduct.tsx +107 -0
- package/templates/nova/src/sections/FeaturedProduct/index.ts +23 -0
- package/templates/nova/src/sections/FeaturedProduct/presets.ts +8 -0
- package/templates/nova/src/sections/FeaturedProduct/schema.ts +35 -0
- package/templates/nova/src/sections/Footer/Footer.css +81 -0
- package/templates/nova/src/sections/Footer/Footer.tsx +256 -0
- package/templates/nova/src/sections/Footer/blocks/FooterBrand.tsx +40 -0
- package/templates/nova/src/sections/Footer/blocks/FooterLine.tsx +11 -0
- package/templates/nova/src/sections/Footer/blocks/FooterMenu.tsx +14 -0
- package/templates/nova/src/sections/Footer/blocks/FooterText.tsx +8 -0
- package/templates/nova/src/sections/Footer/blocks/footer.css +7 -0
- package/templates/nova/src/sections/Footer/index.ts +38 -0
- package/templates/nova/src/sections/Footer/presets.ts +10 -0
- package/templates/nova/src/sections/Footer/schema.ts +27 -0
- package/templates/nova/src/sections/Group/Group.css +59 -0
- package/templates/nova/src/sections/Group/Group.tsx +167 -0
- package/templates/nova/src/sections/Group/blocks/Accordion.tsx +26 -0
- package/templates/nova/src/sections/Group/blocks/JumboText.tsx +20 -0
- package/templates/nova/src/sections/Group/blocks/Spacer.tsx +16 -0
- package/templates/nova/src/sections/Group/index.ts +36 -0
- package/templates/nova/src/sections/Group/presets.ts +12 -0
- package/templates/nova/src/sections/Group/schema.ts +35 -0
- package/templates/nova/src/sections/Header/Header.css +185 -0
- package/templates/nova/src/sections/Header/Header.tsx +478 -0
- package/templates/nova/src/sections/Header/index.ts +32 -0
- package/templates/nova/src/sections/Header/presets.ts +10 -0
- package/templates/nova/src/sections/Header/schema.ts +32 -0
- package/templates/nova/src/sections/Hero/Hero.tsx +128 -0
- package/templates/nova/src/sections/Hero/index.ts +24 -0
- package/templates/nova/src/sections/Hero/presets.ts +16 -0
- package/templates/nova/src/sections/Hero/schema.ts +56 -0
- package/templates/nova/src/sections/ImageGallery/ImageGallery.tsx +86 -0
- package/templates/nova/src/sections/ImageGallery/index.ts +28 -0
- package/templates/nova/src/sections/ImageGallery/presets.ts +10 -0
- package/templates/nova/src/sections/ImageGallery/schema.ts +34 -0
- package/templates/nova/src/sections/ImageWithText/ImageWithText.tsx +62 -0
- package/templates/nova/src/sections/ImageWithText/index.ts +23 -0
- package/templates/nova/src/sections/ImageWithText/presets.ts +12 -0
- package/templates/nova/src/sections/ImageWithText/schema.ts +45 -0
- package/templates/nova/src/sections/LineAddFriend/LineAddFriend.tsx +25 -0
- package/templates/nova/src/sections/LineAddFriend/index.ts +20 -0
- package/templates/nova/src/sections/LineAddFriend/presets.ts +3 -0
- package/templates/nova/src/sections/LineAddFriend/schema.ts +25 -0
- package/templates/nova/src/sections/LogoList/LogoList.tsx +40 -0
- package/templates/nova/src/sections/LogoList/blocks/Logo.tsx +18 -0
- package/templates/nova/src/sections/LogoList/index.ts +29 -0
- package/templates/nova/src/sections/LogoList/presets.ts +8 -0
- package/templates/nova/src/sections/LogoList/schema.ts +29 -0
- package/templates/nova/src/sections/MainCollection/MainCollection.tsx +51 -0
- package/templates/nova/src/sections/MainCollection/index.ts +26 -0
- package/templates/nova/src/sections/MainCollection/presets.ts +10 -0
- package/templates/nova/src/sections/MainCollection/schema.ts +29 -0
- package/templates/nova/src/sections/Marquee/Marquee.tsx +114 -0
- package/templates/nova/src/sections/Marquee/index.ts +20 -0
- package/templates/nova/src/sections/Marquee/presets.ts +10 -0
- package/templates/nova/src/sections/Marquee/schema.ts +23 -0
- package/templates/nova/src/sections/Multicolumn/Multicolumn.tsx +56 -0
- package/templates/nova/src/sections/Multicolumn/index.ts +25 -0
- package/templates/nova/src/sections/Multicolumn/presets.ts +8 -0
- package/templates/nova/src/sections/Multicolumn/schema.ts +34 -0
- package/templates/nova/src/sections/Newsletter/Newsletter.tsx +64 -0
- package/templates/nova/src/sections/Newsletter/index.ts +20 -0
- package/templates/nova/src/sections/Newsletter/presets.ts +10 -0
- package/templates/nova/src/sections/Newsletter/schema.ts +36 -0
- package/templates/nova/src/sections/NotFound/NotFound.tsx +41 -0
- package/templates/nova/src/sections/NotFound/index.ts +20 -0
- package/templates/nova/src/sections/NotFound/presets.ts +6 -0
- package/templates/nova/src/sections/NotFound/schema.ts +30 -0
- package/templates/nova/src/sections/PageBody/PageBody.tsx +35 -0
- package/templates/nova/src/sections/PageBody/index.ts +21 -0
- package/templates/nova/src/sections/PageBody/presets.ts +6 -0
- package/templates/nova/src/sections/PageBody/schema.ts +29 -0
- package/templates/nova/src/sections/PolicyPage/PolicyPage.tsx +62 -0
- package/templates/nova/src/sections/PolicyPage/index.ts +23 -0
- package/templates/nova/src/sections/PolicyPage/presets.ts +6 -0
- package/templates/nova/src/sections/PolicyPage/schema.ts +27 -0
- package/templates/nova/src/sections/ProductDetails/ProductDetails.tsx +269 -0
- package/templates/nova/src/sections/ProductDetails/blocks/AddToCartBlock.tsx +15 -0
- package/templates/nova/src/sections/ProductDetails/blocks/ProductDescription.tsx +15 -0
- package/templates/nova/src/sections/ProductDetails/blocks/ProductInventory.tsx +38 -0
- package/templates/nova/src/sections/ProductDetails/blocks/ProductPrice.tsx +19 -0
- package/templates/nova/src/sections/ProductDetails/blocks/ProductSku.tsx +15 -0
- package/templates/nova/src/sections/ProductDetails/blocks/ProductTitle.tsx +17 -0
- package/templates/nova/src/sections/ProductDetails/blocks/Quantity.tsx +14 -0
- package/templates/nova/src/sections/ProductDetails/blocks/Tab.tsx +23 -0
- package/templates/nova/src/sections/ProductDetails/blocks/VariantPickerBlock.tsx +44 -0
- package/templates/nova/src/sections/ProductDetails/blocks/commerce-blocks.css +13 -0
- package/templates/nova/src/sections/ProductDetails/index.ts +44 -0
- package/templates/nova/src/sections/ProductDetails/presets.ts +10 -0
- package/templates/nova/src/sections/ProductDetails/schema.ts +44 -0
- package/templates/nova/src/sections/ProductGrid/ProductGrid.tsx +38 -0
- package/templates/nova/src/sections/ProductGrid/index.ts +25 -0
- package/templates/nova/src/sections/ProductGrid/presets.ts +6 -0
- package/templates/nova/src/sections/ProductGrid/schema.ts +37 -0
- package/templates/nova/src/sections/ProductRecommendations/ProductRecommendations.tsx +45 -0
- package/templates/nova/src/sections/ProductRecommendations/index.ts +20 -0
- package/templates/nova/src/sections/ProductRecommendations/presets.ts +8 -0
- package/templates/nova/src/sections/ProductRecommendations/schema.ts +28 -0
- package/templates/nova/src/sections/ProductReviews/ProductReviews.tsx +136 -0
- package/templates/nova/src/sections/ProductReviews/index.ts +22 -0
- package/templates/nova/src/sections/ProductReviews/presets.ts +6 -0
- package/templates/nova/src/sections/ProductReviews/schema.ts +31 -0
- package/templates/nova/src/sections/PromoBanner/PromoBanner.tsx +78 -0
- package/templates/nova/src/sections/PromoBanner/index.ts +20 -0
- package/templates/nova/src/sections/PromoBanner/presets.ts +10 -0
- package/templates/nova/src/sections/PromoBanner/schema.ts +38 -0
- package/templates/nova/src/sections/RecentlyViewed/RecentlyViewed.tsx +53 -0
- package/templates/nova/src/sections/RecentlyViewed/index.ts +20 -0
- package/templates/nova/src/sections/RecentlyViewed/presets.ts +6 -0
- package/templates/nova/src/sections/RecentlyViewed/schema.ts +26 -0
- package/templates/nova/src/sections/RichText/RichText.tsx +38 -0
- package/templates/nova/src/sections/RichText/index.ts +20 -0
- package/templates/nova/src/sections/RichText/presets.ts +10 -0
- package/templates/nova/src/sections/RichText/schema.ts +37 -0
- package/templates/nova/src/sections/SearchResults/SearchResults.tsx +112 -0
- package/templates/nova/src/sections/SearchResults/index.ts +26 -0
- package/templates/nova/src/sections/SearchResults/presets.ts +8 -0
- package/templates/nova/src/sections/SearchResults/schema.ts +30 -0
- package/templates/nova/src/sections/ShopTheLook/ShopTheLook.tsx +112 -0
- package/templates/nova/src/sections/ShopTheLook/blocks/Hotspot.tsx +22 -0
- package/templates/nova/src/sections/ShopTheLook/index.ts +33 -0
- package/templates/nova/src/sections/ShopTheLook/presets.ts +8 -0
- package/templates/nova/src/sections/ShopTheLook/schema.ts +31 -0
- package/templates/nova/src/sections/Slideshow/Slideshow.tsx +199 -0
- package/templates/nova/src/sections/Slideshow/blocks/Slide.tsx +93 -0
- package/templates/nova/src/sections/Slideshow/index.ts +30 -0
- package/templates/nova/src/sections/Slideshow/presets.ts +10 -0
- package/templates/nova/src/sections/Slideshow/schema.ts +36 -0
- package/templates/nova/src/sections/StoreLocator/StoreLocator.tsx +66 -0
- package/templates/nova/src/sections/StoreLocator/index.ts +20 -0
- package/templates/nova/src/sections/StoreLocator/presets.ts +6 -0
- package/templates/nova/src/sections/StoreLocator/schema.ts +34 -0
- package/templates/nova/src/sections/TeamFounders/TeamFounders.tsx +74 -0
- package/templates/nova/src/sections/TeamFounders/blocks/Person.tsx +28 -0
- package/templates/nova/src/sections/TeamFounders/index.ts +28 -0
- package/templates/nova/src/sections/TeamFounders/presets.ts +10 -0
- package/templates/nova/src/sections/TeamFounders/schema.ts +36 -0
- package/templates/nova/src/sections/Testimonials/Testimonials.tsx +83 -0
- package/templates/nova/src/sections/Testimonials/blocks/Testimonial.tsx +30 -0
- package/templates/nova/src/sections/Testimonials/index.ts +29 -0
- package/templates/nova/src/sections/Testimonials/presets.ts +10 -0
- package/templates/nova/src/sections/Testimonials/schema.ts +35 -0
- package/templates/nova/src/sections/Timeline/Timeline.tsx +43 -0
- package/templates/nova/src/sections/Timeline/blocks/Step.tsx +19 -0
- package/templates/nova/src/sections/Timeline/index.ts +28 -0
- package/templates/nova/src/sections/Timeline/presets.ts +8 -0
- package/templates/nova/src/sections/Timeline/schema.ts +31 -0
- package/templates/nova/src/sections/TrustBadges/TrustBadges.tsx +42 -0
- package/templates/nova/src/sections/TrustBadges/index.ts +29 -0
- package/templates/nova/src/sections/TrustBadges/presets.ts +8 -0
- package/templates/nova/src/sections/TrustBadges/schema.ts +29 -0
- package/templates/nova/src/sections/UgcGallery/UgcGallery.tsx +58 -0
- package/templates/nova/src/sections/UgcGallery/blocks/UgcImage.tsx +27 -0
- package/templates/nova/src/sections/UgcGallery/index.ts +31 -0
- package/templates/nova/src/sections/UgcGallery/presets.ts +8 -0
- package/templates/nova/src/sections/UgcGallery/schema.ts +35 -0
- package/templates/nova/src/sections/VideoHero/VideoHero.tsx +75 -0
- package/templates/nova/src/sections/VideoHero/index.ts +22 -0
- package/templates/nova/src/sections/VideoHero/presets.ts +8 -0
- package/templates/nova/src/sections/VideoHero/schema.ts +43 -0
- package/templates/nova/src/sections/data.ts +115 -0
- package/templates/nova/src/sections/shipped-copy.ts +25 -0
- package/templates/nova/src/sections/starter.ts +49 -0
- package/templates/nova/src/styles/Container.css +6 -0
- package/templates/nova/src/styles/Container.tsx +13 -0
- package/templates/nova/src/styles/Icon.css +2 -0
- package/templates/nova/src/styles/Icon.tsx +101 -0
- package/templates/nova/src/styles/NovaProvider.tsx +91 -0
- package/templates/nova/src/styles/base.css +142 -0
- package/templates/nova/src/styles/blocks.css +6 -0
- package/templates/nova/src/styles/fonts/OFL.txt +93 -0
- package/templates/nova/src/styles/fonts/geist-cyrillic-ext.woff2 +0 -0
- package/templates/nova/src/styles/fonts/geist-cyrillic.woff2 +0 -0
- package/templates/nova/src/styles/fonts/geist-latin-ext.woff2 +0 -0
- package/templates/nova/src/styles/fonts/geist-latin.woff2 +0 -0
- package/templates/nova/src/styles/fonts/geist-mono-cyrillic-ext.woff2 +0 -0
- package/templates/nova/src/styles/fonts/geist-mono-cyrillic.woff2 +0 -0
- package/templates/nova/src/styles/fonts/geist-mono-latin-ext.woff2 +0 -0
- package/templates/nova/src/styles/fonts/geist-mono-latin.woff2 +0 -0
- package/templates/nova/src/styles/fonts/geist-mono-vietnamese.woff2 +0 -0
- package/templates/nova/src/styles/fonts/geist-vietnamese.woff2 +0 -0
- package/templates/nova/src/styles/fonts.css +95 -0
- package/templates/nova/src/styles/global-settings.css +88 -0
- package/templates/nova/src/styles/index.css +6 -0
- package/templates/nova/src/styles/motion.ts +53 -0
- package/templates/nova/src/styles/scroll.ts +24 -0
- package/templates/nova/src/styles/sections/commerce.css +147 -0
- package/templates/nova/src/styles/sections/content.css +304 -0
- package/templates/nova/src/styles/sections/editorial.css +131 -0
- package/templates/nova/src/styles/sections/promotion.css +336 -0
- package/templates/nova/src/styles/sections/trust.css +117 -0
- package/templates/nova/src/styles/sections/utility.css +190 -0
- package/templates/nova/src/styles/strings.ts +492 -0
- package/templates/nova/src/styles/text.ts +4 -0
- package/templates/nova/src/styles/theme.ts +171 -0
- package/templates/nova/src/styles/tokens.css +600 -0
- package/templates/nova/src/styles/viewport.ts +23 -0
- package/templates/nova/template.json +94 -0
- package/templates/nova/templates/404.json +30 -0
- package/templates/nova/templates/account.json +15 -0
- package/templates/nova/templates/article.json +41 -0
- package/templates/nova/templates/article.longform.json +17 -0
- package/templates/nova/templates/blog.featured.json +22 -0
- package/templates/nova/templates/blog.json +29 -0
- package/templates/nova/templates/cart.json +67 -0
- package/templates/nova/templates/collection.featured.json +43 -0
- package/templates/nova/templates/collection.json +30 -0
- package/templates/nova/templates/contact.json +65 -0
- package/templates/nova/templates/index.json +167 -0
- package/templates/nova/templates/list-collections.json +19 -0
- package/templates/nova/templates/page.about.json +134 -0
- package/templates/nova/templates/page.contact.json +27 -0
- package/templates/nova/templates/page.json +27 -0
- package/templates/nova/templates/policy.json +23 -0
- package/templates/nova/templates/product.bundle.json +145 -0
- package/templates/nova/templates/product.json +119 -0
- package/templates/nova/templates/search.json +17 -0
- package/templates/nova/tests/abi-ai-contract.test.ts +99 -0
- package/templates/nova/tests/abi-forms.test.tsx +269 -0
- package/templates/nova/tests/abi-inventory.test.tsx +197 -0
- package/templates/nova/tests/abi-legacy-settings.test.tsx +105 -0
- package/templates/nova/tests/abi-opt-in.test.tsx +100 -0
- package/templates/nova/tests/abi-parity.test.tsx +95 -0
- package/templates/nova/tests/abi-r5-store-settings.test.tsx +337 -0
- package/templates/nova/tests/abi-render.test.tsx +178 -0
- package/templates/nova/tests/abi-shop-links.test.tsx +141 -0
- package/templates/nova/tests/abi-storefront-basics.test.tsx +233 -0
- package/templates/nova/tests/abi-variant-url.test.tsx +73 -0
- package/templates/nova/tests/add-refused-no-success.test.tsx +193 -0
- package/templates/nova/tests/architecture.test.ts +63 -0
- package/templates/nova/tests/architecture.ts +160 -0
- package/templates/nova/tests/audit-guards.test.ts +31 -0
- package/templates/nova/tests/bundle-split.test.ts +29 -0
- package/templates/nova/tests/cart-limit-message.test.tsx +212 -0
- package/templates/nova/tests/cart-link-message.test.tsx +98 -0
- package/templates/nova/tests/cases/components/Overview.cases.tsx +8 -0
- package/templates/nova/tests/cases/components/base/Accordion.cases.tsx +40 -0
- package/templates/nova/tests/cases/components/base/Badge.cases.tsx +30 -0
- package/templates/nova/tests/cases/components/base/Button.cases.tsx +73 -0
- package/templates/nova/tests/cases/components/base/Checkbox.cases.tsx +27 -0
- package/templates/nova/tests/cases/components/base/Chip.cases.tsx +37 -0
- package/templates/nova/tests/cases/components/base/Divider.cases.tsx +26 -0
- package/templates/nova/tests/cases/components/base/IconButton.cases.tsx +28 -0
- package/templates/nova/tests/cases/components/base/Input.cases.tsx +28 -0
- package/templates/nova/tests/cases/components/base/Link.cases.tsx +27 -0
- package/templates/nova/tests/cases/components/base/Radio.cases.tsx +27 -0
- package/templates/nova/tests/cases/components/base/Select.cases.tsx +26 -0
- package/templates/nova/tests/cases/components/base/Skeleton.cases.tsx +29 -0
- package/templates/nova/tests/cases/components/base/Spinner.cases.tsx +16 -0
- package/templates/nova/tests/cases/components/base/Switch.cases.tsx +26 -0
- package/templates/nova/tests/cases/components/base/Tabs.cases.tsx +39 -0
- package/templates/nova/tests/cases/components/base/Textarea.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/commerce/AddToCart.cases.tsx +36 -0
- package/templates/nova/tests/cases/components/commerce/CartLine.cases.tsx +20 -0
- package/templates/nova/tests/cases/components/commerce/CartSummary.cases.tsx +16 -0
- package/templates/nova/tests/cases/components/commerce/CollectionCard.cases.tsx +19 -0
- package/templates/nova/tests/cases/components/commerce/ComparisonTable.cases.tsx +19 -0
- package/templates/nova/tests/cases/components/commerce/Hotspot.cases.tsx +25 -0
- package/templates/nova/tests/cases/components/commerce/LookItem.cases.tsx +19 -0
- package/templates/nova/tests/cases/components/commerce/PaymentMark.cases.tsx +16 -0
- package/templates/nova/tests/cases/components/commerce/Price.cases.tsx +27 -0
- package/templates/nova/tests/cases/components/commerce/ProductCard.cases.tsx +67 -0
- package/templates/nova/tests/cases/components/commerce/ProductGallery.cases.tsx +39 -0
- package/templates/nova/tests/cases/components/commerce/QuantitySelector.cases.tsx +31 -0
- package/templates/nova/tests/cases/components/commerce/Reviews.cases.tsx +35 -0
- package/templates/nova/tests/cases/components/commerce/Stars.cases.tsx +16 -0
- package/templates/nova/tests/cases/components/commerce/VariantPicker.cases.tsx +38 -0
- package/templates/nova/tests/cases/components/commerce/blocks/Badge.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/commerce/blocks/PaymentIcons.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/commerce/blocks/Swatches.cases.tsx +22 -0
- package/templates/nova/tests/cases/components/content/ArticleCard.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/content/BeforeAfterSlider.cases.tsx +18 -0
- package/templates/nova/tests/cases/components/content/CardRow.cases.tsx +23 -0
- package/templates/nova/tests/cases/components/content/CarouselControls.cases.tsx +48 -0
- package/templates/nova/tests/cases/components/content/ContentsNav.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/content/Countdown.cases.tsx +28 -0
- package/templates/nova/tests/cases/components/content/HeroCopy.cases.tsx +42 -0
- package/templates/nova/tests/cases/components/content/HeroFrame.cases.tsx +22 -0
- package/templates/nova/tests/cases/components/content/HeroMedia.cases.tsx +18 -0
- package/templates/nova/tests/cases/components/content/LongForm.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/content/MediaFrame.cases.tsx +32 -0
- package/templates/nova/tests/cases/components/content/RichText.cases.tsx +19 -0
- package/templates/nova/tests/cases/components/content/SectionHead.cases.tsx +19 -0
- package/templates/nova/tests/cases/components/content/ShareButtons.cases.tsx +24 -0
- package/templates/nova/tests/cases/components/content/StoreCard.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/content/TestimonialCard.cases.tsx +19 -0
- package/templates/nova/tests/cases/components/content/UgcTile.cases.tsx +18 -0
- package/templates/nova/tests/cases/components/content/blocks/Button.cases.tsx +20 -0
- package/templates/nova/tests/cases/components/content/blocks/Column.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/content/blocks/Heading.cases.tsx +18 -0
- package/templates/nova/tests/cases/components/content/blocks/Icon.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/content/blocks/Image.cases.tsx +22 -0
- package/templates/nova/tests/cases/components/content/blocks/Text.cases.tsx +18 -0
- package/templates/nova/tests/cases/components/content/blocks/Video.cases.tsx +18 -0
- package/templates/nova/tests/cases/components/forms/ContactForm.cases.tsx +29 -0
- package/templates/nova/tests/cases/components/forms/FormField.cases.tsx +61 -0
- package/templates/nova/tests/cases/components/forms/NewsletterForm.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/forms/SearchField.cases.tsx +25 -0
- package/templates/nova/tests/cases/components/forms/blocks/Newsletter.cases.tsx +19 -0
- package/templates/nova/tests/cases/components/navigation/BrandLogo.cases.tsx +29 -0
- package/templates/nova/tests/cases/components/navigation/Breadcrumb.cases.tsx +19 -0
- package/templates/nova/tests/cases/components/navigation/LocaleSelector.cases.tsx +32 -0
- package/templates/nova/tests/cases/components/navigation/Menu.cases.tsx +27 -0
- package/templates/nova/tests/cases/components/navigation/Pagination.cases.tsx +25 -0
- package/templates/nova/tests/cases/components/navigation/RecoveryLinks.cases.tsx +16 -0
- package/templates/nova/tests/cases/components/navigation/SocialLinks.cases.tsx +18 -0
- package/templates/nova/tests/cases/components/navigation/blocks/SocialLinks.cases.tsx +17 -0
- package/templates/nova/tests/cases/components/overlays/AccountMenu.cases.tsx +28 -0
- package/templates/nova/tests/cases/components/overlays/CartDrawer.cases.tsx +30 -0
- package/templates/nova/tests/cases/components/overlays/ConfirmationDialog.cases.tsx +16 -0
- package/templates/nova/tests/cases/components/overlays/CookieConsent.cases.tsx +23 -0
- package/templates/nova/tests/cases/components/overlays/CookiePreferences.cases.tsx +16 -0
- package/templates/nova/tests/cases/components/overlays/Drawer.cases.tsx +52 -0
- package/templates/nova/tests/cases/components/overlays/MarketSelector.cases.tsx +82 -0
- package/templates/nova/tests/cases/components/overlays/Menu.cases.tsx +27 -0
- package/templates/nova/tests/cases/components/overlays/MobileNavDrawer.cases.tsx +32 -0
- package/templates/nova/tests/cases/components/overlays/Modal.cases.tsx +71 -0
- package/templates/nova/tests/cases/components/overlays/Popover.cases.tsx +50 -0
- package/templates/nova/tests/cases/components/overlays/PromoPopup.cases.tsx +19 -0
- package/templates/nova/tests/cases/components/overlays/QuickView.cases.tsx +18 -0
- package/templates/nova/tests/cases/components/overlays/SearchModal.cases.tsx +60 -0
- package/templates/nova/tests/cases/components/overlays/Sheet.cases.tsx +32 -0
- package/templates/nova/tests/cases/components/overlays/SizeGuide.cases.tsx +27 -0
- package/templates/nova/tests/cases/components/overlays/Toast.cases.tsx +48 -0
- package/templates/nova/tests/cases/components/overlays/Tooltip.cases.tsx +33 -0
- package/templates/nova/tests/cases/components/states/Alert.cases.tsx +27 -0
- package/templates/nova/tests/cases/components/states/EditorFrame.cases.tsx +36 -0
- package/templates/nova/tests/cases/components/states/Empty.cases.tsx +30 -0
- package/templates/nova/tests/cases/components/states/Error.cases.tsx +38 -0
- package/templates/nova/tests/cases/components/states/Loading.cases.tsx +15 -0
- package/templates/nova/tests/cases/components/states/MediaState.cases.tsx +29 -0
- package/templates/nova/tests/cases/components/states/SkeletonLayout.cases.tsx +20 -0
- package/templates/nova/tests/cases/components/states/Unavailable.cases.tsx +16 -0
- package/templates/nova/tests/cases/components/states/media/MediaError.cases.tsx +18 -0
- package/templates/nova/tests/cases/components/states/media/MediaPlaceholder.cases.tsx +87 -0
- package/templates/nova/tests/cases/sections/Overview.cases.tsx +8 -0
- package/templates/nova/tests/cases/sections/commerce/collection-list/CollectionList.cases.tsx +46 -0
- package/templates/nova/tests/cases/sections/commerce/collection-list/blocks/CollectionItem.cases.tsx +18 -0
- package/templates/nova/tests/cases/sections/commerce/featured-collection/FeaturedCollection.cases.tsx +46 -0
- package/templates/nova/tests/cases/sections/commerce/featured-product/FeaturedProduct.cases.tsx +40 -0
- package/templates/nova/tests/cases/sections/commerce/main-collection/MainCollection.cases.tsx +81 -0
- package/templates/nova/tests/cases/sections/commerce/product-details/ProductDetails.cases.tsx +110 -0
- package/templates/nova/tests/cases/sections/commerce/product-details/blocks/AddToCartBlock.cases.tsx +21 -0
- package/templates/nova/tests/cases/sections/commerce/product-details/blocks/ProductDescription.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/commerce/product-details/blocks/ProductInventory.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/commerce/product-details/blocks/ProductPrice.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/commerce/product-details/blocks/ProductSku.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/commerce/product-details/blocks/ProductTitle.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/commerce/product-details/blocks/Quantity.cases.tsx +20 -0
- package/templates/nova/tests/cases/sections/commerce/product-details/blocks/Tab.cases.tsx +29 -0
- package/templates/nova/tests/cases/sections/commerce/product-details/blocks/VariantPickerBlock.cases.tsx +32 -0
- package/templates/nova/tests/cases/sections/commerce/product-grid/ProductGrid.cases.tsx +48 -0
- package/templates/nova/tests/cases/sections/commerce/product-recommendations/ProductRecommendations.cases.tsx +27 -0
- package/templates/nova/tests/cases/sections/commerce/recently-viewed/RecentlyViewed.cases.tsx +36 -0
- package/templates/nova/tests/cases/sections/content/article-body/ArticleBody.cases.tsx +32 -0
- package/templates/nova/tests/cases/sections/content/before-after/BeforeAfter.cases.tsx +40 -0
- package/templates/nova/tests/cases/sections/content/before-after/blocks/Comparison.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/content/blog-posts/BlogPosts.cases.tsx +47 -0
- package/templates/nova/tests/cases/sections/content/collection-links/CollectionLinks.cases.tsx +36 -0
- package/templates/nova/tests/cases/sections/content/faq/Faq.cases.tsx +38 -0
- package/templates/nova/tests/cases/sections/content/faq/blocks/FaqItem.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/content/feature-grid-blocks/FeatureGridBlocks.cases.tsx +31 -0
- package/templates/nova/tests/cases/sections/content/feature-highlights/FeatureHighlights.cases.tsx +26 -0
- package/templates/nova/tests/cases/sections/content/image-gallery/ImageGallery.cases.tsx +31 -0
- package/templates/nova/tests/cases/sections/content/image-with-text/ImageWithText.cases.tsx +35 -0
- package/templates/nova/tests/cases/sections/content/multicolumn/Multicolumn.cases.tsx +32 -0
- package/templates/nova/tests/cases/sections/content/page-body/PageBody.cases.tsx +31 -0
- package/templates/nova/tests/cases/sections/content/rich-text/RichTextSection.cases.tsx +33 -0
- package/templates/nova/tests/cases/sections/content/team-founders/TeamFounders.cases.tsx +31 -0
- package/templates/nova/tests/cases/sections/content/team-founders/blocks/Person.cases.tsx +18 -0
- package/templates/nova/tests/cases/sections/content/timeline/Timeline.cases.tsx +27 -0
- package/templates/nova/tests/cases/sections/content/timeline/blocks/Step.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/layout/announcement-bar/AnnouncementBar.cases.tsx +119 -0
- package/templates/nova/tests/cases/sections/layout/breadcrumbs/Breadcrumbs.cases.tsx +73 -0
- package/templates/nova/tests/cases/sections/layout/divider/Divider.cases.tsx +81 -0
- package/templates/nova/tests/cases/sections/layout/footer/Footer.cases.tsx +116 -0
- package/templates/nova/tests/cases/sections/layout/footer/blocks/FooterBrand.cases.tsx +27 -0
- package/templates/nova/tests/cases/sections/layout/footer/blocks/FooterMenu.cases.tsx +22 -0
- package/templates/nova/tests/cases/sections/layout/footer/blocks/FooterText.cases.tsx +20 -0
- package/templates/nova/tests/cases/sections/layout/group/Group.cases.tsx +185 -0
- package/templates/nova/tests/cases/sections/layout/group/blocks/Accordion.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/layout/group/blocks/JumboText.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/layout/group/blocks/Spacer.cases.tsx +33 -0
- package/templates/nova/tests/cases/sections/layout/header/Header.cases.tsx +277 -0
- package/templates/nova/tests/cases/sections/marketing/collection-banner/CollectionBanner.cases.tsx +62 -0
- package/templates/nova/tests/cases/sections/marketing/hero/Hero.cases.tsx +138 -0
- package/templates/nova/tests/cases/sections/marketing/marquee/Marquee.cases.tsx +63 -0
- package/templates/nova/tests/cases/sections/marketing/promo-banner/PromoBanner.cases.tsx +87 -0
- package/templates/nova/tests/cases/sections/marketing/slideshow/Slideshow.cases.tsx +113 -0
- package/templates/nova/tests/cases/sections/marketing/slideshow/blocks/Slide.cases.tsx +26 -0
- package/templates/nova/tests/cases/sections/marketing/video-hero/VideoHero.cases.tsx +72 -0
- package/templates/nova/tests/cases/sections/trust/comparison-table/ComparisonTableSection.cases.tsx +42 -0
- package/templates/nova/tests/cases/sections/trust/comparison-table/blocks/CompareItem.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/trust/logo-list/LogoList.cases.tsx +29 -0
- package/templates/nova/tests/cases/sections/trust/logo-list/blocks/Logo.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/trust/product-reviews/ProductReviews.cases.tsx +51 -0
- package/templates/nova/tests/cases/sections/trust/shop-the-look/ShopTheLook.cases.tsx +51 -0
- package/templates/nova/tests/cases/sections/trust/shop-the-look/blocks/Hotspot.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/trust/testimonials/Testimonials.cases.tsx +61 -0
- package/templates/nova/tests/cases/sections/trust/testimonials/blocks/Testimonial.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/trust/trust-badges/TrustBadges.cases.tsx +30 -0
- package/templates/nova/tests/cases/sections/trust/ugc-gallery/UgcGallery.cases.tsx +32 -0
- package/templates/nova/tests/cases/sections/trust/ugc-gallery/blocks/UgcImage.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/utility/account/Account.cases.tsx +44 -0
- package/templates/nova/tests/cases/sections/utility/cart-items/CartItems.cases.tsx +43 -0
- package/templates/nova/tests/cases/sections/utility/cart-items/blocks/CartBadge.cases.tsx +16 -0
- package/templates/nova/tests/cases/sections/utility/contact-form/ContactFormSection.cases.tsx +52 -0
- package/templates/nova/tests/cases/sections/utility/contact-form/blocks/ContactDetail.cases.tsx +17 -0
- package/templates/nova/tests/cases/sections/utility/newsletter/NewsletterSection.cases.tsx +109 -0
- package/templates/nova/tests/cases/sections/utility/not-found/NotFound.cases.tsx +28 -0
- package/templates/nova/tests/cases/sections/utility/policy-page/PolicyPage.cases.tsx +29 -0
- package/templates/nova/tests/cases/sections/utility/search-results/SearchResultsSection.cases.tsx +39 -0
- package/templates/nova/tests/cases/sections/utility/store-locator/StoreLocator.cases.tsx +40 -0
- package/templates/nova/tests/cases/styles/Borders.cases.tsx +10 -0
- package/templates/nova/tests/cases/styles/Breakpoints.cases.tsx +10 -0
- package/templates/nova/tests/cases/styles/Colors.cases.tsx +10 -0
- package/templates/nova/tests/cases/styles/Elevation.cases.tsx +10 -0
- package/templates/nova/tests/cases/styles/Icon.cases.tsx +23 -0
- package/templates/nova/tests/cases/styles/Layout.cases.tsx +8 -0
- package/templates/nova/tests/cases/styles/Motion.cases.tsx +10 -0
- package/templates/nova/tests/cases/styles/Overview.cases.tsx +8 -0
- package/templates/nova/tests/cases/styles/Radius.cases.tsx +10 -0
- package/templates/nova/tests/cases/styles/SharedThemeSettings.cases.tsx +19 -0
- package/templates/nova/tests/cases/styles/Spacing.cases.tsx +10 -0
- package/templates/nova/tests/cases/styles/Typography.cases.tsx +10 -0
- package/templates/nova/tests/cases.test.tsx +38 -0
- package/templates/nova/tests/components/PromoPopup.test.ts +21 -0
- package/templates/nova/tests/components/RichText.test.tsx +65 -0
- package/templates/nova/tests/components/formatDate.test.ts +10 -0
- package/templates/nova/tests/contract-locks.test.ts +23 -0
- package/templates/nova/tests/contracts.test.ts +153 -0
- package/templates/nova/tests/cookie-consent-privacy.test.tsx +128 -0
- package/templates/nova/tests/empty-store.test.tsx +81 -0
- package/templates/nova/tests/fixtures/commerce.ts +128 -0
- package/templates/nova/tests/fixtures/content.ts +58 -0
- package/templates/nova/tests/fixtures/index.ts +8 -0
- package/templates/nova/tests/fixtures/merch.ts +161 -0
- package/templates/nova/tests/fixtures/navigation.ts +125 -0
- package/templates/nova/tests/fixtures/promotion.ts +43 -0
- package/templates/nova/tests/fixtures/sample-store.json +1674 -0
- package/templates/nova/tests/fixtures/shop.ts +88 -0
- package/templates/nova/tests/fixtures/stress.ts +21 -0
- package/templates/nova/tests/inert.test.tsx +285 -0
- package/templates/nova/tests/line-add-friend.test.tsx +108 -0
- package/templates/nova/tests/localized-copy.test.ts +330 -0
- package/templates/nova/tests/product-template-blocks.test.ts +28 -0
- package/templates/nova/tests/rewards-launcher.test.tsx +401 -0
- package/templates/nova/tests/schema.test.ts +65 -0
- package/templates/nova/tests/schema.typetest.ts +7 -0
- package/templates/nova/tests/section-groups.test.ts +189 -0
- package/templates/nova/tests/sections/Group.test.tsx +37 -0
- package/templates/nova/tests/setup.ts +30 -0
- package/templates/nova/tests/starter.test.ts +56 -0
- package/templates/nova/tests/storefront-click-path.test.tsx +157 -0
- package/templates/nova/tests/styles/button-brand.test.ts +24 -0
- package/templates/nova/tests/styles/theme.test.ts +43 -0
- package/templates/nova/tests/styles/tokens.test.ts +35 -0
- package/templates/nova/tests/support/abi-ctx.ts +136 -0
- package/templates/nova/tests/support/block-stories.tsx +13 -0
- package/templates/nova/tests/support/case.tsx +102 -0
- package/templates/nova/tests/support/contracts.ts +30 -0
- package/templates/nova/tests/support/foundation-pages.tsx +260 -0
- package/templates/nova/tests/support/ia-pages.tsx +154 -0
- package/templates/nova/tests/support/listing-demo.tsx +29 -0
- package/templates/nova/tests/support/presets.ts +14 -0
- package/templates/nova/tests/support/sections.ts +36 -0
- package/templates/nova/tests/support/story-content.ts +321 -0
- package/templates/nova/tests/support/story-helpers.tsx +37 -0
- package/templates/nova/tests/support/with-nova.tsx +26 -0
- package/templates/nova/tests/theme-config.test.ts +82 -0
- package/templates/nova/tests/theme-integrity.test.ts +315 -0
- package/templates/nova/theme.config.ts +134 -0
- package/templates/nova/theme.manifest.json +17495 -0
- package/templates/nova/tsconfig.json +19 -0
- package/templates/nova/vite.abi-ssr.config.ts +37 -0
- package/templates/nova/vite.abi.config.ts +26 -0
- package/templates/nova/vitest.config.ts +20 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { FAMILIES, type Applicability } from '../../scripts/compiler/contracts/index'
|
|
3
|
+
import { SECTION_CATEGORIES, sectionsByCategory } from './sections'
|
|
4
|
+
import { Table, mono, muted, page } from './foundation-pages'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The information-architecture pages: the Overviews (Styles → Components →
|
|
8
|
+
* Sections → Layouts) plus Styles / Layout and the shared section settings.
|
|
9
|
+
* Sections and Shared Settings render FROM the live schemas and approved contracts.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const Flow = () => (
|
|
13
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12, ...mono, fontSize: 13 }} aria-label="Styles, then Components, then Sections, then Layouts">
|
|
14
|
+
{['01 STYLES', '02 COMPONENTS', '03 SECTIONS', '04 LAYOUTS'].map((s, i) => (
|
|
15
|
+
<span key={s} style={{ display: 'contents' }}>
|
|
16
|
+
{i > 0 && <span aria-hidden="true">→</span>}
|
|
17
|
+
<span style={{ padding: '8px 12px', border: '1px solid var(--color-fg)', borderRadius: 'var(--radius-sm)' }}>{s}</span>
|
|
18
|
+
</span>
|
|
19
|
+
))}
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const Rule = ({ children }: { children: ReactNode }) => (
|
|
24
|
+
<p style={{ margin: 0, padding: '12px 16px', borderInlineStart: '3px solid var(--color-fg)', background: 'var(--color-bg-muted)', fontSize: 14 }}>{children}</p>
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const Groups = ({ rows }: { rows: [string, string, string][] }) => <Table head={['group', 'what lives here', 'pages']} rows={rows.map(([g, w, p]) => [<strong>{g}</strong>, w, <span style={muted}>{p}</span>])} />
|
|
28
|
+
|
|
29
|
+
export function StylesOverview() {
|
|
30
|
+
return (
|
|
31
|
+
<div style={page}>
|
|
32
|
+
<h1 className="nv-h2">Styles</h1>
|
|
33
|
+
<Flow />
|
|
34
|
+
<p>Styles are the visual system, not components: tokens, type, spacing, layout, motion, icons and the settings shared across the theme. They import nothing from the rest of Nova; everything else is built from them.</p>
|
|
35
|
+
<Rule>Styles define the visual system. Change a token or a theme setting here and every component, section and layout follows.</Rule>
|
|
36
|
+
<Groups
|
|
37
|
+
rows={[
|
|
38
|
+
['Foundations', 'the Nova 01 token families, each shown beside its live computed value', 'Colors · Typography · Spacing · Radius · Borders · Shadows · Motion · Breakpoints · Icons'],
|
|
39
|
+
['Layout', 'page gutter, grid and header height per breakpoint', 'Layout'],
|
|
40
|
+
['Settings', 'global theme settings and the section settings every section shares', 'Shared Theme Settings'],
|
|
41
|
+
]}
|
|
42
|
+
/>
|
|
43
|
+
<p style={muted}>Source: src/styles/ (tokens.css, base.css, global-settings.css, theme.ts, NovaProvider, Icon, strings).</p>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function ComponentsOverview() {
|
|
49
|
+
return (
|
|
50
|
+
<div style={page}>
|
|
51
|
+
<h1 className="nv-h2">Components</h1>
|
|
52
|
+
<Flow />
|
|
53
|
+
<p>Components are the reusable building blocks: typed props, never fetch, never declare merchant settings — except the reusable blocks in components/*/blocks/, which carry a persisted block schema because several sections accept them.</p>
|
|
54
|
+
<Rule>Components must never import Sections. The architecture test (test/architecture.ts) fails the build if one does.</Rule>
|
|
55
|
+
<Groups
|
|
56
|
+
rows={[
|
|
57
|
+
['Base', 'controls every other part uses', 'Button · IconButton · Link · Badge · Input · Textarea · Select · Checkbox · Radio · Switch · Divider · Tabs · Accordion · Skeleton · Spinner · Chip'],
|
|
58
|
+
['Commerce', 'product, price and cart parts', 'ProductCard · CollectionCard · Price · ProductBadge · Rating · VariantPicker · Swatches · QuantitySelector · …'],
|
|
59
|
+
['Content', 'media and text', 'Heading · RichText · MediaFrame · Image · Video · Carousel · …'],
|
|
60
|
+
['Navigation', 'ways through the store', 'BrandLogo · Menu · Breadcrumb · Pagination · LocaleSelector · …'],
|
|
61
|
+
['Forms', 'fields and small complete forms', 'FormField · NewsletterForm · ContactFields · SearchField'],
|
|
62
|
+
['Overlays', 'surfaces over the page', 'Modal · Drawer · Popover · Tooltip · CartDrawer · MobileNavDrawer · SearchModal · AccountMenu · QuickView · SizeGuide · MarketSelector · …'],
|
|
63
|
+
['States', 'what a part shows while loading, empty, failed or unset', 'Loading · Empty · Error · Unavailable · EditorSetup · MediaPlaceholder · …'],
|
|
64
|
+
]}
|
|
65
|
+
/>
|
|
66
|
+
<p style={muted}>Source: src/components/base · commerce · content · navigation · forms · overlays · states.</p>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function SectionsOverview() {
|
|
72
|
+
const groups = sectionsByCategory()
|
|
73
|
+
return (
|
|
74
|
+
<div style={page}>
|
|
75
|
+
<h1 className="nv-h2">Sections</h1>
|
|
76
|
+
<Flow />
|
|
77
|
+
<p>Sections are what a merchant adds, reorders and configures. Every one is listed once in the theme.config.ts — the list the editor's Add section picker reads. Categories are navigation only; the design batch is metadata.</p>
|
|
78
|
+
<Rule>Sections are built from Components. Components never import Sections; Sections never import Layouts.</Rule>
|
|
79
|
+
<Table
|
|
80
|
+
head={['category', 'id', 'label', 'blocks', 'presets', 'status']}
|
|
81
|
+
rows={SECTION_CATEGORIES.flatMap((c) =>
|
|
82
|
+
groups[c].map((s, i) => [
|
|
83
|
+
i === 0 ? <strong>{c}</strong> : '',
|
|
84
|
+
<code style={mono}>{s.id}</code>,
|
|
85
|
+
s.label,
|
|
86
|
+
<span style={muted}>{s.blocks.join(' · ') || '—'}</span>,
|
|
87
|
+
s.presets.length || '—',
|
|
88
|
+
<span style={mono}>{s.status}</span>,
|
|
89
|
+
]),
|
|
90
|
+
)}
|
|
91
|
+
/>
|
|
92
|
+
<p style={muted}>Source: src/sections/<category>/<id>/ — a block only that section accepts lives in its blocks/ folder.</p>
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function LayoutPage() {
|
|
98
|
+
return (
|
|
99
|
+
<div style={page}>
|
|
100
|
+
<h1 className="nv-h2">Layout</h1>
|
|
101
|
+
<p style={muted}>Page gutter, grid and header height per breakpoint (phone < 640 · tablet 640 · desktop 1024 · wide 1280), from tokens.css. Containers are on Breakpoints; section spacing is on Spacing.</p>
|
|
102
|
+
<Table
|
|
103
|
+
head={['token', '< 640', '640', '1024', '1280']}
|
|
104
|
+
rows={[
|
|
105
|
+
['--page-gutter', '16', '24', '40', '40'],
|
|
106
|
+
['--grid-columns', '4', '8', '12', '12'],
|
|
107
|
+
['--grid-gap', '12', '16', '16', '16'],
|
|
108
|
+
['--grid-products', '2', '3', '3', '4'],
|
|
109
|
+
['--header-height', '56', '60', '72', '72'],
|
|
110
|
+
].map(([t, ...v]) => [<code style={mono}>{t}</code>, ...v])}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const SHARED = FAMILIES['12A'].shared
|
|
117
|
+
/** The Nova 12 section families, each with its own shared-applicability table. */
|
|
118
|
+
const SECTION_FAMILIES = ['12A', '12B', '12C', '12D', '12E', '12F', '12G'] as const
|
|
119
|
+
|
|
120
|
+
/** How many sections, across the Nova 12 families, apply / override / skip a shared setting. */
|
|
121
|
+
function applicability(prop: string) {
|
|
122
|
+
const count: Record<Applicability, number> = { APPLIES: 0, 'OVERRIDDEN BY SECTION-SPECIFIC BEHAVIOR': 0, 'NOT APPLICABLE': 0 }
|
|
123
|
+
for (const f of SECTION_FAMILIES) {
|
|
124
|
+
const row = FAMILIES[f].sharedApplicability.find((a) => a.prop === prop)
|
|
125
|
+
if (!row) continue
|
|
126
|
+
for (const [k, v] of Object.entries(row)) if (k !== 'prop' && !k.endsWith('Note') && v in count) count[v]++
|
|
127
|
+
}
|
|
128
|
+
return count
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function SharedSettingsPage() {
|
|
132
|
+
return (
|
|
133
|
+
<div style={page}>
|
|
134
|
+
<h1 className="nv-h2">Shared Settings</h1>
|
|
135
|
+
<p style={muted}>The Nova 12 shared vocabulary (12A · 00): section-level settings that keep the same ID, options and meaning in every section that uses them. Each section records its own default. Rendered from the approved contract (nova-12A.json · shared) and every family's applicability table; code: src/sections/shared.ts.</p>
|
|
136
|
+
<Table
|
|
137
|
+
head={['setting', 'label', 'control', 'options (persisted IDs)', 'group default', 'applies · overridden · n/a']}
|
|
138
|
+
rows={SHARED.map((r) => {
|
|
139
|
+
const a = applicability(r.name)
|
|
140
|
+
return [
|
|
141
|
+
<code style={mono}>{r.name}</code>,
|
|
142
|
+
r.label,
|
|
143
|
+
<code style={mono}>{r.control}</code>,
|
|
144
|
+
r.options.length ? <span style={mono}>{r.options.map((o, i) => `${o}${r.optionLabels[i] && r.optionLabels[i] !== o ? ` (${r.optionLabels[i]})` : ''}`).join(' · ')}</span> : '—',
|
|
145
|
+
<code style={mono}>{String(r.def ?? '') || '—'}</code>,
|
|
146
|
+
<span style={mono}>{a.APPLIES} · {a['OVERRIDDEN BY SECTION-SPECIFIC BEHAVIOR']} · {a['NOT APPLICABLE']}</span>,
|
|
147
|
+
]
|
|
148
|
+
})}
|
|
149
|
+
/>
|
|
150
|
+
<h2 className="nv-h3">Notes from the contract</h2>
|
|
151
|
+
<Table head={['setting', 'responsive', 'description']} rows={SHARED.map((r) => [<code style={mono}>{r.name}</code>, <span style={muted}>{r.resp || '—'}</span>, <span style={muted}>{r.desc || '—'}</span>])} />
|
|
152
|
+
</div>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import type { Facet, Product } from '@tanqory/theme-kit'
|
|
3
|
+
import * as fx from '../fixtures/index'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lab-only host for the listing sections: keeps facet / sort state and
|
|
7
|
+
* "fetches" by filtering fixtures, the way a storefront host would with the
|
|
8
|
+
* platform API. Components stay presentational.
|
|
9
|
+
*/
|
|
10
|
+
export function useListingDemo(start: { facets?: Facet[]; products?: Product[] } = {}) {
|
|
11
|
+
const [facets, setFacets] = useState<Facet[]>(start.facets ?? fx.merchFacets)
|
|
12
|
+
const [sort, setSort] = useState('featured')
|
|
13
|
+
const all = start.products ?? fx.merchListing
|
|
14
|
+
const active = facets.flatMap((f) => (f.values ?? []).filter((v) => v.active).map((v) => v.id))
|
|
15
|
+
// Fixture rule: "textiles" and "out" match nothing; anything else keeps the list.
|
|
16
|
+
const products = active.some((id) => id === 'textiles') ? [] : all
|
|
17
|
+
return {
|
|
18
|
+
facets,
|
|
19
|
+
sort,
|
|
20
|
+
sortOptions: fx.merchSortOptions,
|
|
21
|
+
products,
|
|
22
|
+
total: products.length,
|
|
23
|
+
onToggleFilter: (facetId: string, valueId: string) =>
|
|
24
|
+
setFacets((fs) => fs.map((f) => (f.id !== facetId ? f : { ...f, values: f.values?.map((v) => (v.id === valueId ? { ...v, active: !v.active } : v)) }))),
|
|
25
|
+
onPriceFilter: (facetId: string, from?: number, to?: number) => setFacets((fs) => fs.map((f) => (f.id === facetId && f.range ? { ...f, range: { ...f.range, from, to } } : f))),
|
|
26
|
+
onClearFilters: () => setFacets((fs) => fs.map((f) => ({ ...f, values: f.values?.map((v) => ({ ...v, active: false })), range: f.range ? { ...f.range, from: undefined, to: undefined } : undefined }))),
|
|
27
|
+
onSortChange: setSort,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { resolveSettings, type Schema, type SettingsOf } from '@tanqory/theme-kit'
|
|
2
|
+
import { presetsFor } from '../../scripts/compiler/contracts/index'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A contract preset as story args: ONLY the settings the preset names,
|
|
6
|
+
* resolved through the schema (legacy / coercion). Spread it over a story's
|
|
7
|
+
* content so the preset never resets copy or data to schema defaults.
|
|
8
|
+
*/
|
|
9
|
+
export function presetArgs<S extends Schema>(schema: S, name: string): Partial<SettingsOf<S>> {
|
|
10
|
+
const p = presetsFor(schema.name).find((x) => x.name === name)
|
|
11
|
+
if (!p) throw new Error(`no preset "${name}" for ${schema.name}`)
|
|
12
|
+
const all = resolveSettings(schema, p.settings).settings as Record<string, unknown>
|
|
13
|
+
return Object.fromEntries(Object.keys(p.settings).filter((k) => k in all).map((k) => [k, all[k]])) as Partial<SettingsOf<S>>
|
|
14
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Schema, ThemeSection } from '@tanqory/theme-kit'
|
|
2
|
+
import theme from '../../theme.config'
|
|
3
|
+
import { presetsFor } from '../../scripts/compiler/contracts/index'
|
|
4
|
+
|
|
5
|
+
/** A section as the test pages show it: theme.config's entry plus its contract label/presets. */
|
|
6
|
+
export type SectionEntry = Omit<ThemeSection, 'presets' | 'blocks'> & {
|
|
7
|
+
label: string
|
|
8
|
+
/** Accepted block names (schema.blocks); the schemas themselves are `blockSchemas`. */
|
|
9
|
+
blocks: readonly string[]
|
|
10
|
+
blockSchemas: readonly Schema[]
|
|
11
|
+
presets: readonly { name: string }[]
|
|
12
|
+
status: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const SECTIONS: readonly SectionEntry[] = theme.sections.map((s) => ({
|
|
16
|
+
...s,
|
|
17
|
+
label: s.title ?? s.id,
|
|
18
|
+
blocks: s.schema.blocks ?? [],
|
|
19
|
+
blockSchemas: s.blocks ?? [],
|
|
20
|
+
presets: s.presets ?? presetsFor(s.id),
|
|
21
|
+
status: 'READY',
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
export const sectionRegistry: Readonly<Record<string, SectionEntry>> = Object.fromEntries(SECTIONS.map((s) => [s.id, s]))
|
|
25
|
+
export const SECTION_SCHEMAS: readonly Schema[] = SECTIONS.map((s) => s.schema)
|
|
26
|
+
/** Picker groups, in the order theme.config first uses them. */
|
|
27
|
+
export const SECTION_CATEGORIES: readonly string[] = [...new Set(SECTIONS.map((s) => s.category))]
|
|
28
|
+
|
|
29
|
+
export function sectionsByCategory(): Record<string, SectionEntry[]> {
|
|
30
|
+
const out: Record<string, SectionEntry[]> = Object.fromEntries(SECTION_CATEGORIES.map((c) => [c, []]))
|
|
31
|
+
for (const s of SECTIONS) out[s.category].push(s)
|
|
32
|
+
return out
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Every block schema the theme's sections declare (a section owns its blocks; there is no separate list). */
|
|
36
|
+
export const BLOCK_SCHEMAS: readonly Schema[] = [...new Set(theme.sections.flatMap((s) => s.blocks ?? []))]
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { placeholderImage } from '../../src/components/states/media/MediaPlaceholder'
|
|
2
|
+
import type { Cart, CartLine, Collection, CustomerAccount, NovaImage, NovaLink, NovaMenu, NovaVideo, PaymentMethod, Policy, Product, ProductVariant, Review, ReviewsData, SearchResults, StoreLocation } from '@tanqory/theme-kit'
|
|
3
|
+
import { resolveSettings } from '@tanqory/theme-kit'
|
|
4
|
+
import { columnSchema, type ColumnInstance } from '../../src/components/content/blocks/Column'
|
|
5
|
+
import { stepSchema, type StepInstance } from '../../src/sections/Timeline/blocks/Step'
|
|
6
|
+
import type { FaqItemSettings } from '../../src/sections/Faq/blocks/FaqItem'
|
|
7
|
+
import type { BadgeBlockSettings } from '../../src/components/commerce/blocks/Badge'
|
|
8
|
+
import { testimonialSchema, type TestimonialInstance } from '../../src/sections/Testimonials/blocks/Testimonial'
|
|
9
|
+
import { logoSchema, type LogoInstance } from '../../src/sections/LogoList/blocks/Logo'
|
|
10
|
+
import { ugcImageSchema, type UgcPhotoInstance } from '../../src/sections/UgcGallery/blocks/UgcImage'
|
|
11
|
+
import { hotspotSchema, type HotspotInstance } from '../../src/sections/ShopTheLook/blocks/Hotspot'
|
|
12
|
+
import { compareItemSchema, type CompareItemInstance } from '../../src/sections/ComparisonTable/blocks/CompareItem'
|
|
13
|
+
import { merchBoard, merchCandle, merchCarafe, merchDuvet, merchDuvetBlend, merchDuvetPercale, merchMug, merchNapkins, merchOil, merchPlatter, merchPourOver, merchSimple, merchThrow } from '../fixtures/merch'
|
|
14
|
+
import type { ContactDetailSettings } from '../../src/sections/ContactForm/blocks/ContactDetail'
|
|
15
|
+
|
|
16
|
+
/** The canvas cart's candle is in stock (the listing fixture sells it out). */
|
|
17
|
+
const merchCandleInStock: Product = { ...merchCandle, available: true, variants: merchCandle.variants.map((v) => ({ ...v, available: true, quantityAvailable: 24 })) }
|
|
18
|
+
|
|
19
|
+
/** 12D · the approved canvas copy (Halden story page). */
|
|
20
|
+
const col = (s: Record<string, unknown>, family?: Parameters<typeof placeholderImage>[0]): ColumnInstance => ({
|
|
21
|
+
settings: resolveSettings(columnSchema, s).settings,
|
|
22
|
+
image: family ? placeholderImage(family, 900, 600, String(s.heading)) : null,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export const storyColumns: ColumnInstance[] = [
|
|
26
|
+
col({ heading: 'Small batches', body: 'Every glaze run is forty pieces or fewer, so slight variation is part of the design.', linkLabel: 'How we make it', link: '/pages/process' }, 'editorial'),
|
|
27
|
+
col({ heading: 'Honest materials', body: 'European flax, FSC walnut, lead-free stoneware. Origins are listed on every product.', linkLabel: 'Our materials', link: '/pages/materials' }, 'product'),
|
|
28
|
+
col({ heading: 'Repair, not replace', body: 'Chipped a mug in year one? We re-glaze or replace it at no cost.', linkLabel: 'Our guarantee', link: '/pages/guarantee' }, 'store'),
|
|
29
|
+
]
|
|
30
|
+
export const storyIconColumns: ColumnInstance[] = [
|
|
31
|
+
col({ icon: 'truck', heading: 'Free shipping', body: 'Over $120' }),
|
|
32
|
+
col({ icon: 'return', heading: '30-day returns', body: 'Collected from your door' }),
|
|
33
|
+
col({ icon: 'shield', heading: 'Repair guarantee', body: 'Year one, free' }),
|
|
34
|
+
col({ icon: 'leaf', heading: 'Carbon-neutral', body: 'Every delivery' }),
|
|
35
|
+
]
|
|
36
|
+
export const storyBento: ColumnInstance[] = [
|
|
37
|
+
{ ...col({ heading: 'European flax, grown without irrigation.', body: 'Materials' }), image: placeholderImage('hero', 1200, 1000, 'Flax field') },
|
|
38
|
+
col({ icon: 'leaf', heading: 'Rain-fed flax', body: 'No irrigation, no pesticides.' }),
|
|
39
|
+
col({ icon: 'clock', heading: 'Stonewashed 3 hours', body: 'Soft from the first night.' }),
|
|
40
|
+
col({ icon: 'shield', heading: 'Oeko-Tex certified', body: 'Tested for 350 substances.' }),
|
|
41
|
+
col({ icon: 'return', heading: 'Gets better with washing', body: 'Stronger when wet.' }),
|
|
42
|
+
]
|
|
43
|
+
export const storyFeatures = 'truck: Free shipping | Over $120\nreturn: 30-day returns | Collected from your door\nshield: Repair guarantee | Year one, free\nleaf: Carbon-neutral | Every delivery'
|
|
44
|
+
|
|
45
|
+
export const storyFaq: FaqItemSettings[] = [
|
|
46
|
+
{ question: 'How long does delivery take?', answer: '<p>Orders ship from Chiang Mai within two business days and arrive in 2–4 days across Thailand. International orders leave every Tuesday and Friday.</p>' },
|
|
47
|
+
{ question: 'Can I put stoneware in the dishwasher?', answer: '<p>Yes — everything except pieces with a gold rim. Hand-washing keeps the glaze glossy for longer.</p>' },
|
|
48
|
+
{ question: 'What if something arrives broken?', answer: '<p>Send us a photo within 7 days and we send a replacement, no return needed.</p>' },
|
|
49
|
+
{ question: 'Do you offer gift wrapping?', answer: '<p>Every order can be wrapped in recycled kraft paper with a handwritten card.</p>' },
|
|
50
|
+
{ question: 'How do returns work?', answer: '<p>30 days, collected from your door. Refunds land within 5 business days.</p>' },
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
const step = (s: Record<string, unknown>): StepInstance => ({ settings: resolveSettings(stepSchema, s).settings, image: placeholderImage('editorial', 800, 600, String(s.heading)) })
|
|
54
|
+
export const storySteps: StepInstance[] = [
|
|
55
|
+
step({ label: 'Day 1', heading: 'Throw', body: 'Local clay is thrown on the wheel and left to firm overnight.' }),
|
|
56
|
+
step({ label: 'Day 3', heading: 'Trim & dry', body: 'Each piece is trimmed by hand, then dries for a week.' }),
|
|
57
|
+
step({ label: 'Day 10', heading: 'Glaze', body: 'Dipped in Sea Salt glaze; the iron speckles appear in the kiln.' }),
|
|
58
|
+
step({ label: 'Day 12', heading: 'Fire', body: 'Fired to 1,240°C, then checked one by one before packing.' }),
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
const u = (h: string) => `/collections/${h}`
|
|
62
|
+
export const storyBrowseMenu: NovaMenu = {
|
|
63
|
+
handle: 'browse',
|
|
64
|
+
title: 'Browse the shop',
|
|
65
|
+
items: [
|
|
66
|
+
{ title: 'Kitchen & Table', url: u('kitchen-table') },
|
|
67
|
+
{ title: 'Bedroom & Bath', url: u('bedroom-bath') },
|
|
68
|
+
{ title: 'Pantry', url: u('pantry') },
|
|
69
|
+
{ title: 'Candles & Scent', url: u('candles') },
|
|
70
|
+
{ title: 'Gift Sets', url: u('gift-sets') },
|
|
71
|
+
{ title: 'New in', url: u('new') },
|
|
72
|
+
{ title: 'Sale', url: u('sale') },
|
|
73
|
+
{ title: 'Gift Cards', url: '/products/gift-card' },
|
|
74
|
+
{ title: 'Studio Seconds', url: u('seconds') },
|
|
75
|
+
{ title: 'Workshops', url: '/pages/workshops' },
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const storyGallery = [
|
|
80
|
+
{ type: 'image' as const, settings: { caption: 'The kiln at dawn' }, image: placeholderImage('hero', 1200, 1200, 'The kiln at dawn') },
|
|
81
|
+
{ type: 'image' as const, settings: {}, image: placeholderImage('product', 800, 800, 'Glaze buckets') },
|
|
82
|
+
{ type: 'image' as const, settings: { caption: 'Sea Salt test tiles' }, image: placeholderImage('collection', 800, 800, 'Sea Salt test tiles') },
|
|
83
|
+
{ type: 'image' as const, settings: {}, image: placeholderImage('editorial', 800, 800, 'Drying racks') },
|
|
84
|
+
{ type: 'image' as const, settings: {}, image: placeholderImage('store', 800, 800, 'The studio shop') },
|
|
85
|
+
]
|
|
86
|
+
export const storyImage = placeholderImage('editorial', 1000, 1250, 'A potter glazing a bowl')
|
|
87
|
+
|
|
88
|
+
// ── 12E · editorial (the canvas copy) ─────────────────────────────────────
|
|
89
|
+
import type { Article, Blog } from '@tanqory/theme-kit'
|
|
90
|
+
import { personSchema, type PersonInstance } from '../../src/sections/TeamFounders/blocks/Person'
|
|
91
|
+
import type { ComparisonInstance } from '../../src/sections/BeforeAfter/blocks/Comparison'
|
|
92
|
+
import { comparisonSchema } from '../../src/sections/BeforeAfter/blocks/Comparison'
|
|
93
|
+
|
|
94
|
+
export const storyArticleHtml = `<p>Wash at 40°C with a mild detergent, and <a href="/pages/care">skip the softener</a> — it coats the fibres and stops them breathing. Line-dry when you can; if you tumble, take it out while still slightly damp.</p>
|
|
95
|
+
<h2>Four steps</h2>
|
|
96
|
+
<ol><li>Turn covers inside out and close the buttons.</li><li>Wash cool, on a gentle cycle.</li><li>Line dry, or tumble on low until just damp.</li><li>Shake out and smooth by hand — no iron needed.</li></ol>
|
|
97
|
+
<blockquote>"Linen is the only fabric that gets better the more you use it."</blockquote>
|
|
98
|
+
<h2>Care symbols</h2>
|
|
99
|
+
<table><thead><tr><th>Item</th><th>Wash</th><th>Dry</th></tr></thead><tbody><tr><td>Duvet cover</td><td>40°C gentle</td><td>Line / low</td></tr><tr><td>Napkins</td><td>60°C</td><td>Tumble low</td></tr></tbody></table>
|
|
100
|
+
<h2>Storing</h2>
|
|
101
|
+
<ul><li>Store dry, folded loosely.</li><li>Small pulls: trim, never pull.</li></ul>
|
|
102
|
+
<h2>When to replace</h2>
|
|
103
|
+
<p>Linen doesn't need ironing. Shake it out, smooth it by hand, and let the creases settle overnight.</p>
|
|
104
|
+
<hr><p>Last reviewed by the studio team. <strong>Questions?</strong> Email hello@halden.co.</p>`
|
|
105
|
+
|
|
106
|
+
const art = (id: string, title: string, tag: string, date: string, family: Parameters<typeof placeholderImage>[0] | null, excerpt?: string): Article => ({
|
|
107
|
+
id, handle: id, title, excerpt, url: `/blogs/journal/${id}`, image: family ? placeholderImage(family, 1200, 800, title) : null, author: 'Mali Srisuk', publishedAt: date, tags: [tag], blogHandle: 'journal',
|
|
108
|
+
})
|
|
109
|
+
export const storyArticle: Article & { contentHtml: string } = {
|
|
110
|
+
...art('how-to-wash-linen', 'How to wash linen so it lasts twenty years', 'care', '2026-09-12', 'product', "Heat and softener are linen's only real enemies. A four-step routine for sheets, napkins and everything between."),
|
|
111
|
+
tags: ['care', 'linen'],
|
|
112
|
+
contentHtml: storyArticleHtml,
|
|
113
|
+
}
|
|
114
|
+
export const storyJournal: Blog = {
|
|
115
|
+
id: 'journal', handle: 'journal', title: 'Journal', url: '/blogs/journal',
|
|
116
|
+
articles: [
|
|
117
|
+
storyArticle,
|
|
118
|
+
art('inside-the-kiln', 'Inside the kiln: firing the Sea Salt glaze', 'studio', '2026-08-28', 'editorial', 'Twelve hours, 1,240°C and a very early morning.'),
|
|
119
|
+
art('slow-sunday-breakfast', 'A slow Sunday breakfast for four', 'recipes', '2026-08-09', 'collection', 'Eggs, sourdough and the Pour-Over set, start to finish.'),
|
|
120
|
+
art('oiling-walnut', 'Oiling a walnut board: a five-minute ritual', 'care', '2026-07-03', null, 'Once a month keeps the grain from drying out.'),
|
|
121
|
+
art('meet-niran', 'Meet Niran, our glaze chemist', 'makers', '2026-06-18', 'store', 'Four hundred test tiles for one colour.'),
|
|
122
|
+
art('table-for-summer', 'Setting a table for summer', 'studio', '2026-06-02', 'hero', 'Three layers, no rules.'),
|
|
123
|
+
art('flax-harvest', 'The flax harvest in Normandy', 'makers', '2026-05-15', 'editorial', 'Where our linen starts.'),
|
|
124
|
+
],
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const person = (s: Record<string, unknown>, family: Parameters<typeof placeholderImage>[0] | null): PersonInstance => ({ settings: resolveSettings(personSchema, s).settings, image: family ? placeholderImage(family, 800, 1000, String(s.name)) : null })
|
|
128
|
+
export const storyPeople: PersonInstance[] = [
|
|
129
|
+
person({ name: 'Mali Srisuk', role: 'Founder & potter', quote: "I started Halden because I couldn't find a mug I wanted to keep for twenty years.", bio: 'Mali trained in Mashiko before opening a two-kiln studio outside Chiang Mai in 2016. She still throws every new shape herself.', linkLabel: "Read Mali's story", link: '/pages/mali' }, 'editorial'),
|
|
130
|
+
person({ name: 'Niran Chaiya', role: 'Glaze chemist', bio: 'Four hundred test tiles for one colour.' }, 'store'),
|
|
131
|
+
person({ name: 'Ploy Anan', role: 'Linen & textiles' }, null),
|
|
132
|
+
person({ name: 'Tom Becker', role: 'Woodwork' }, 'product'),
|
|
133
|
+
]
|
|
134
|
+
const cmp = (s: Record<string, unknown>): ComparisonInstance => ({ settings: resolveSettings(comparisonSchema, s).settings, before: placeholderImage('hero', 1200, 800, 'The chipped rim, before'), after: placeholderImage('product', 1200, 800, 'The rim re-glazed in Sea Salt') })
|
|
135
|
+
export const storyComparisons: ComparisonInstance[] = [
|
|
136
|
+
cmp({ caption: 'Chipped in the dishwasher; re-glazed in Sea Salt.', alt: 'A mug with a chipped rim, then the same mug re-glazed and smooth' }),
|
|
137
|
+
cmp({ caption: 'A cracked bowl, kintsugi-style repair.', alt: 'A cracked bowl, then repaired with a visible seam' }),
|
|
138
|
+
cmp({ caption: 'A stained board, oiled and sanded.', alt: 'A stained walnut board, then oiled and even' }),
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
/* ── 12F · Trust & Advanced Commerce — the approved canvas copy. Quotes, logos and
|
|
142
|
+
badges are the merchant's; reviews stand in for a future reviews source. */
|
|
143
|
+
const tm = (s: Record<string, unknown>, avatar: boolean, video?: NovaVideo): TestimonialInstance => ({
|
|
144
|
+
settings: resolveSettings(testimonialSchema, s).settings,
|
|
145
|
+
avatar: avatar ? placeholderImage('avatar', 400, 400, String(s.name)) : null,
|
|
146
|
+
video: video ?? null,
|
|
147
|
+
})
|
|
148
|
+
export const storyTestimonials: TestimonialInstance[] = [
|
|
149
|
+
tm({ quote: '"The linen is softer every wash, and the Oat colour is perfect in morning light."', name: 'Ploy R.', role: 'Bangkok', rating: 5 }, true),
|
|
150
|
+
tm({ quote: '"Our mugs chipped after two years and Halden re-glazed them for free. Who does that?"', name: 'Tom W.', role: 'Melbourne', rating: 5 }, false),
|
|
151
|
+
tm({ quote: '"Slow shipping to Europe, but worth every day of waiting."', name: 'Anna L.', role: 'Stockholm', rating: 4 }, true),
|
|
152
|
+
tm({ quote: '"The walnut board is the first thing guests ask about."', name: 'Kenji M.', role: 'Osaka', rating: 5 }, false),
|
|
153
|
+
tm({ quote: '"Ordered napkins for a wedding table — every one arrived pressed and perfect."', name: 'Sara B.', role: 'Lisbon', rating: 5 }, true),
|
|
154
|
+
tm({ quote: '"Customer service answered a glaze question in an hour, on a Sunday."', name: 'Niran P.', role: 'Chiang Mai', rating: 0 }, false),
|
|
155
|
+
]
|
|
156
|
+
export const storySpotlight: TestimonialInstance = tm(
|
|
157
|
+
{ quote: '"Three years of daily use and the mugs still look new. The repair service is the real thing."', name: 'Anna Lindqvist', role: 'Stockholm', rating: 5 },
|
|
158
|
+
true,
|
|
159
|
+
{ src: 'media/studio-loop.webm', poster: placeholderImage('editorial', 600, 800, 'Anna Lindqvist at her kitchen table'), captions: 'media/testimonial-anna.en.vtt', captionsLang: 'en' },
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const logo = (slug: string, name: string, w: number): LogoInstance => ({ settings: resolveSettings(logoSchema, { alt: name, href: '' }).settings, image: { src: `media/logos/${slug}.svg`, alt: name, width: w, height: 32 } })
|
|
163
|
+
/** Fictional press names — a lab must not ship real publications' marks. */
|
|
164
|
+
export const storyLogos: LogoInstance[] = [
|
|
165
|
+
logo('northlight', 'Northlight', 273),
|
|
166
|
+
logo('field-and-form', 'Field & Form', 185),
|
|
167
|
+
logo('slow-house', 'Slow House', 275),
|
|
168
|
+
logo('atelier', 'Atelier', 106),
|
|
169
|
+
logo('the-table', 'The Table', 206),
|
|
170
|
+
logo('harbour', 'Harbour', 205),
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
export const storyBadges: BadgeBlockSettings[] = [
|
|
174
|
+
{ icon: 'truck', label: 'Free shipping over $120', detail: 'Carbon-neutral, 2–4 days', link: '/policies/shipping' },
|
|
175
|
+
{ icon: 'return', label: '30-day returns', detail: 'Collected from your door', link: '/policies/returns' },
|
|
176
|
+
{ icon: 'shield', label: 'Repair guarantee', detail: 'Re-glazed free in year one', link: '' },
|
|
177
|
+
{ icon: 'card', label: 'Secure checkout', detail: 'Card or PromptPay', link: '' },
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
const rv = (id: string, rating: Review['rating'], title: string, body: string, author: string, createdAt: string, variant: string, photos = 0): Review => ({
|
|
181
|
+
id, rating, title, body, author, createdAt, variant, verified: true,
|
|
182
|
+
photos: Array.from({ length: photos }, (_, i) => placeholderImage('product', 800, 800, `${author}'s photo ${i + 1}`)),
|
|
183
|
+
})
|
|
184
|
+
export const storyReviews: ReviewsData = {
|
|
185
|
+
average: 4.8,
|
|
186
|
+
count: 212,
|
|
187
|
+
distribution: [174, 25, 8, 3, 2],
|
|
188
|
+
reviews: [
|
|
189
|
+
rv('r1', 5, 'Softer every single wash', 'Bought the Queen in Oat. It arrived a little stiff, and after three washes it drapes beautifully. Sleeps cool in Bangkok heat.', 'Ploy R.', '2026-09-02', 'Queen · Oat', 2),
|
|
190
|
+
rv('r2', 5, 'Worth the price', 'The stitching is excellent and the buttons are hidden well. Colour exactly as pictured.', 'James K.', '2026-08-21', 'King · Chalk'),
|
|
191
|
+
rv('r3', 4, 'Lovely, runs slightly large', 'Beautiful linen. The Queen is a little generous on our mattress — fine for us, but check the size guide.', 'Mai T.', '2026-08-03', 'Queen · Clay'),
|
|
192
|
+
rv('r4', 5, 'Our third one', 'We keep buying these for the guest rooms. Still the nicest bedding we own.', 'Hannah S.', '2026-07-19', 'Full · Chalk', 1),
|
|
193
|
+
rv('r5', 3, 'Beautiful, but creases', 'Exactly as described — it is linen, so it creases. I knew that, but it is more than I expected.', 'Daniel O.', '2026-07-02', 'Queen · Sage'),
|
|
194
|
+
rv('r6', 5, 'Perfect in the heat', 'Cool at night even without air-conditioning.', 'Wan C.', '2026-06-14', 'King · Charcoal'),
|
|
195
|
+
rv('r7', 2, 'Colour lighter than photos', 'The Clay is paler than on my screen. Quality is good.', 'Petra V.', '2026-05-30', 'Queen · Clay'),
|
|
196
|
+
],
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const ugc = (handle: string, family: Parameters<typeof placeholderImage>[0], product?: Product): UgcPhotoInstance => ({
|
|
200
|
+
settings: resolveSettings(ugcImageSchema, { caption: handle, alt: `A customer's home, shared by ${handle}`, link: product?.url ?? '' }).settings,
|
|
201
|
+
image: placeholderImage(family, 800, 800, `Shared by ${handle}`),
|
|
202
|
+
product: product ?? null,
|
|
203
|
+
})
|
|
204
|
+
export const storyUgc: UgcPhotoInstance[] = [
|
|
205
|
+
ugc('@ploy.home', 'ugc', merchDuvet),
|
|
206
|
+
ugc('@slowmornings', 'hero', merchMug),
|
|
207
|
+
ugc('@anna.l', 'editorial'),
|
|
208
|
+
ugc('@tomw', 'collection', merchBoard),
|
|
209
|
+
ugc('@mai.t', 'store', merchCarafe),
|
|
210
|
+
ugc('@kenji.cooks', 'product', merchPourOver),
|
|
211
|
+
ugc('@lisbonlinen', 'ugc', merchNapkins),
|
|
212
|
+
ugc('@wan.c', 'generic'),
|
|
213
|
+
ugc('@hannah.s', 'hero', merchThrow),
|
|
214
|
+
ugc('@petra.v', 'editorial', merchCandle),
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
const spot = (product: Product | null, x: number, y: number): HotspotInstance => ({ settings: resolveSettings(hotspotSchema, { x, y }).settings, product })
|
|
218
|
+
export const storyLookImage: NovaImage = placeholderImage('hero', 1000, 1250, 'A slow Sunday corner: a mug, a linen throw and a walnut board by the window')
|
|
219
|
+
export const storyHotspots: HotspotInstance[] = [spot(merchMug, 30, 62), spot(merchThrow, 55, 40), spot(merchBoard, 72, 78)]
|
|
220
|
+
|
|
221
|
+
const cmpItem = (product: Product | null, title: string, values: string): CompareItemInstance => ({ settings: resolveSettings(compareItemSchema, { title, values }).settings, product, image: null })
|
|
222
|
+
export const storyCompareFeatures = ['Fabric', 'Feel', 'Best for', 'Oeko-Tex', 'Needs ironing', 'Warranty'].join('\n')
|
|
223
|
+
export const storyCompareItems: CompareItemInstance[] = [
|
|
224
|
+
cmpItem(merchDuvet, 'Washed Linen', ['100% European flax', 'Soft, relaxed', 'All seasons', 'yes', 'no', '5 years'].join('\n')),
|
|
225
|
+
cmpItem(merchDuvetBlend, 'Linen-Cotton', ['55% linen, 45% cotton', 'Smooth, light crease', 'Warm climates', 'yes', 'no', '3 years'].join('\n')),
|
|
226
|
+
cmpItem(merchDuvetPercale, 'Organic Percale', ['100% organic cotton', 'Crisp, cool', 'Hot sleepers', 'yes', 'Optional', '3 years'].join('\n')),
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
/* ── 12G · Utility & Customer Experience — the approved canvas copy. */
|
|
230
|
+
const USD = (amount: number) => ({ amount, currencyCode: 'USD' })
|
|
231
|
+
const lineOf = (id: string, product: Product, variantTitle: string | null, quantity: number, over: Partial<ProductVariant> = {}): CartLine => {
|
|
232
|
+
const v = { ...(variantTitle ? product.variants.find((x) => x.title === variantTitle)! : product.variants[0]), image: product.featuredImage, ...over }
|
|
233
|
+
return {
|
|
234
|
+
id, quantity, variant: v,
|
|
235
|
+
product: { title: product.title, handle: product.handle, url: product.url, vendor: product.vendor },
|
|
236
|
+
cost: { total: USD(v.price.amount * quantity), compareAtTotal: v.compareAtPrice ? USD(v.compareAtPrice.amount * quantity) : null },
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export const storeCartLines: CartLine[] = [
|
|
240
|
+
lineOf('l1', { ...merchDuvet, title: 'Washed Linen Duvet Cover — Queen' }, 'Queen / Oat', 1, { quantityAvailable: 12 }),
|
|
241
|
+
lineOf('l2', merchMug, 'Oat', 2, { quantityAvailable: 3 }),
|
|
242
|
+
lineOf('l3', merchCandleInStock, null, 1),
|
|
243
|
+
]
|
|
244
|
+
export const storeCart: Cart = { id: 'gid://cart/12g', lines: storeCartLines, note: '', itemCount: 4, subtotal: USD(281), total: USD(281) }
|
|
245
|
+
export const storeCartBadges = [
|
|
246
|
+
{ label: '30-day returns, collected from your door', link: '' },
|
|
247
|
+
{ label: 'Repair guarantee in year one', link: '/pages/guarantee' },
|
|
248
|
+
]
|
|
249
|
+
export const storePayments: PaymentMethod[] = ['visa', 'mastercard', 'amex', 'promptpay']
|
|
250
|
+
export const storeNewSeason: Collection = { id: 'new', handle: 'new-this-season', title: 'New this season', url: '/collections/new-this-season', productsCount: 4, products: [merchMug, merchThrow, merchOil, merchCarafe] }
|
|
251
|
+
|
|
252
|
+
export const storeSearch: SearchResults = {
|
|
253
|
+
query: 'linen',
|
|
254
|
+
products: [merchDuvet, merchNapkins, merchThrow, merchSimple, merchDuvetBlend, merchDuvetPercale, merchMug, merchBoard],
|
|
255
|
+
articles: storyJournal.articles.slice(0, 3),
|
|
256
|
+
pages: [{ title: 'Linen care guide', url: '/pages/care', excerpt: 'Wash at 40°C, skip the softener, line-dry when you can.' }],
|
|
257
|
+
total: 12,
|
|
258
|
+
}
|
|
259
|
+
export const storeSuggestions: NovaLink[] = [
|
|
260
|
+
{ title: 'Bedroom & Bath', url: '/collections/bedroom-bath' },
|
|
261
|
+
{ title: 'Kitchen & Table', url: '/collections/kitchen-table' },
|
|
262
|
+
{ title: 'New in', url: '/collections/new' },
|
|
263
|
+
{ title: 'Gift Sets', url: '/collections/gift-sets' },
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
export const storeCustomer: CustomerAccount = {
|
|
267
|
+
firstName: 'Mali', lastName: 'Srisuk', email: 'mali@halden.co',
|
|
268
|
+
orders: [
|
|
269
|
+
{ id: 'o1', number: '#HC-10482', processedAt: '2026-09-14', status: 'fulfilled', total: USD(245), itemCount: 3, url: '/account/orders/10482' },
|
|
270
|
+
{ id: 'o2', number: '#HC-10417', processedAt: '2026-08-02', status: 'fulfilled', total: USD(74), itemCount: 1, url: '/account/orders/10417' },
|
|
271
|
+
{ id: 'o3', number: '#HC-10309', processedAt: '2026-06-21', status: 'fulfilled', total: USD(36), itemCount: 1, url: '/account/orders/10309' },
|
|
272
|
+
],
|
|
273
|
+
addresses: [],
|
|
274
|
+
}
|
|
275
|
+
export const storeLoginImage: NovaImage = placeholderImage('editorial', 900, 1100, 'Folded linen on a studio shelf')
|
|
276
|
+
|
|
277
|
+
export const storeRefundPolicy: Policy = {
|
|
278
|
+
handle: 'refund-policy', title: 'Refund policy', url: '/policies/refund-policy',
|
|
279
|
+
bodyHtml: `<h2>Returns within 30 days</h2><p>Unused items in their original packaging can be returned within 30 days of delivery. We collect from your door at no cost within Thailand.</p>
|
|
280
|
+
<h2>Refunds</h2><p>Refunds go back to the original payment method within 5 business days of the return reaching our studio.</p>
|
|
281
|
+
<h2>Exchanges</h2><p>Want a different size or colour? Start a return and place a new order — it's the fastest way to get the one you want.</p>
|
|
282
|
+
<h2>Damaged items</h2><p>If something arrives chipped or torn, send us a photo within 7 days and we'll replace it or re-glaze it.</p>
|
|
283
|
+
<h2>Contact</h2><p>Questions about a return? Email hello@halden.co and we'll reply within one business day.</p>`,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const store = (id: string, name: string, address: string[], phone?: string, hours?: string, family?: Parameters<typeof placeholderImage>[0]): StoreLocation => ({ id, name, address, phone, hours, image: family ? placeholderImage(family, 800, 450, `${name} storefront`) : null })
|
|
287
|
+
export const storeLocations: StoreLocation[] = [
|
|
288
|
+
store('s1', 'Nimman Studio', ['88 Nimmanhaemin Soi 9', 'Chiang Mai 50200'], '+66 53 123 456', 'Mon–Sun, 10 am–7 pm', 'store'),
|
|
289
|
+
store('s2', 'Siam Discovery', ['194 Phaya Thai Rd, 4F', 'Bangkok 10330'], '+66 2 123 4567', undefined, 'editorial'),
|
|
290
|
+
store('s3', 'Thong Lo', ['335 Thong Lo 17', 'Bangkok 10110'], '+66 2 765 4321', 'Mon–Sun, 10 am–9 pm', 'collection'),
|
|
291
|
+
store('s4', 'Old Town', ['12 Thanon Ratchadamnoen', 'Chiang Mai 50200'], undefined, undefined, 'hero'),
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
export const storeContactDetails: ContactDetailSettings[] = [
|
|
295
|
+
{ label: 'Email', value: 'hello@halden.co', link: 'mailto:hello@halden.co' },
|
|
296
|
+
{ label: 'Phone', value: '+66 53 123 456', link: 'tel:+6653123456' },
|
|
297
|
+
{ label: 'Studio', value: 'Mon–Fri, 9 am–5 pm', link: '' },
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
/* ── 13 · Overlays — the canvas copy. */
|
|
301
|
+
const guideTable = (unit: 'cm' | 'in') => {
|
|
302
|
+
const rows = unit === 'cm'
|
|
303
|
+
? [['Twin', '172 cm', '218 cm', '90–100 cm'], ['Full', '200 cm', '218 cm', '135–140 cm'], ['Queen', '228 cm', '233 cm', '150–160 cm'], ['King', '264 cm', '233 cm', '180–200 cm']]
|
|
304
|
+
: [['Twin', '68 in', '86 in', '35–39 in'], ['Full', '79 in', '86 in', '53–55 in'], ['Queen', '90 in', '92 in', '59–63 in'], ['King', '104 in', '92 in', '71–79 in']]
|
|
305
|
+
return `<table><thead><tr><th>Size</th><th>Width</th><th>Length</th><th>Fits mattress</th></tr></thead><tbody>${rows.map((r) => `<tr><th>${r[0]}</th>${r.slice(1).map((c) => `<td>${c}</td>`).join('')}</tr>`).join('')}</tbody></table>
|
|
306
|
+
<h2>How to measure</h2><ol><li>Measure your duvet insert, not the bed.</li><li>Width across the top edge, length down the side.</li><li>Choose the cover within 5 cm of the insert.</li></ol>`
|
|
307
|
+
}
|
|
308
|
+
export const storeSizeGuideCm = guideTable('cm')
|
|
309
|
+
export const storeSizeGuideIn = guideTable('in')
|
|
310
|
+
export const storeDrawerCart: Cart = {
|
|
311
|
+
...storeCart,
|
|
312
|
+
lines: [
|
|
313
|
+
lineOf('d1', { ...merchDuvet, title: 'Washed Linen Duvet Cover — Queen, Oat' }, 'Queen / Oat', 1, { quantityAvailable: 12 }),
|
|
314
|
+
lineOf('d2', { ...merchPlatter, title: 'Extra-Large Hand-Thrown Speckled Stoneware Serving Bowl with Walnut Lid, Set of Two' }, null, 1, { price: USD(1249), compareAtPrice: null, quantityAvailable: 6 }),
|
|
315
|
+
lineOf('d3', merchCandle, null, 1, { available: false }),
|
|
316
|
+
],
|
|
317
|
+
itemCount: 3,
|
|
318
|
+
discounts: [{ code: 'AUTUMN10', amount: USD(143.8) }],
|
|
319
|
+
subtotal: USD(1438),
|
|
320
|
+
total: USD(1294.2),
|
|
321
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
/** A labelled specimen row for component stories. */
|
|
4
|
+
export function Row({ label, children, style }: { label?: string; children: ReactNode; style?: CSSProperties }): JSX.Element {
|
|
5
|
+
return (
|
|
6
|
+
<div style={{ display: 'grid', gap: 8, marginBottom: 24 }}>
|
|
7
|
+
{label && <p style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--color-fg-muted)', margin: 0 }}>{label}</p>}
|
|
8
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12, ...style }}>{children}</div>
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Pad({ children, surface }: { children: ReactNode; surface?: 'muted' | 'inverse' }): JSX.Element {
|
|
14
|
+
const bg = surface === 'inverse' ? 'var(--color-bg-inverse)' : surface === 'muted' ? 'var(--color-bg-muted)' : undefined
|
|
15
|
+
return (
|
|
16
|
+
<div style={{ padding: 32, background: bg, color: surface === 'inverse' ? 'var(--color-fg-inverse)' : undefined }} data-nv-surface={surface}>
|
|
17
|
+
{children}
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A section's Overview: the named stories stacked, each under a mono caption.
|
|
24
|
+
* `render` is how one set of args renders (the section component).
|
|
25
|
+
*/
|
|
26
|
+
export function Stack<A>({ base, items, render }: { base: A; items: { name?: string; args?: Partial<A> }[]; render: (args: A) => JSX.Element | null }): JSX.Element {
|
|
27
|
+
return (
|
|
28
|
+
<div style={{ display: 'grid', gap: 48, paddingBlock: 24 }}>
|
|
29
|
+
{items.map((s) => (
|
|
30
|
+
<section key={s.name} aria-label={s.name}>
|
|
31
|
+
<p style={{ font: '500 11px/1 var(--font-mono)', color: 'var(--color-fg-muted)', padding: '0 var(--page-gutter) 12px', margin: 0 }}>{s.name}</p>
|
|
32
|
+
{render({ ...base, ...s.args } as A)}
|
|
33
|
+
</section>
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Decorator } from './case'
|
|
2
|
+
import { NovaProvider } from '../../src/styles/NovaProvider'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Every story renders inside <NovaProvider> — tokens, base styles, lang/dir,
|
|
6
|
+
* storefront vs editor mode, global theme settings. A story's own
|
|
7
|
+
* `parameters.nova` (or the toolbar globals) pins them.
|
|
8
|
+
*/
|
|
9
|
+
export const withNova: Decorator = (Story, ctx) => {
|
|
10
|
+
const pinned = (ctx.parameters.nova ?? {}) as { locale?: string; mode?: 'storefront' | 'editor'; scheme?: 'light' | 'dark'; motion?: 'standard' | 'reduced'; theme?: object }
|
|
11
|
+
const locale = pinned.locale ?? (ctx.globals.locale as string) ?? 'en'
|
|
12
|
+
const mode = pinned.mode ?? (ctx.globals.mode as 'storefront' | 'editor') ?? 'storefront'
|
|
13
|
+
const scheme = pinned.scheme ?? (ctx.globals.scheme as 'light' | 'dark') ?? 'light'
|
|
14
|
+
const motion = pinned.motion ?? (ctx.globals.motion as 'standard' | 'reduced') ?? 'standard'
|
|
15
|
+
const dir = /^ar/.test(locale) ? 'rtl' : 'ltr'
|
|
16
|
+
if (typeof document !== 'undefined') {
|
|
17
|
+
document.documentElement.lang = locale
|
|
18
|
+
document.documentElement.dir = dir
|
|
19
|
+
document.body.style.background = scheme === 'dark' ? '#0e0e0e' : '#ffffff'
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
<NovaProvider locale={locale} dir={dir} mode={mode} theme={{ ...(pinned.theme ?? {}), colorScheme: scheme, motion }} className="nv-lab-root">
|
|
23
|
+
<Story />
|
|
24
|
+
</NovaProvider>
|
|
25
|
+
)
|
|
26
|
+
}
|