@salla.sa/twilight-theme-engine 1.0.85 → 1.0.86
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/dist/{AddToCartForm-B5C2M4OZ.js → AddToCartForm-5X2WZDWR.js} +3 -3
- package/dist/{AddToCartForm-B5C2M4OZ.js.map → AddToCartForm-5X2WZDWR.js.map} +1 -1
- package/dist/{BlogAuthorPage-GTF4UGSS.js → BlogAuthorPage-LO7FSSJR.js} +12 -10
- package/dist/BlogAuthorPage-LO7FSSJR.js.map +1 -0
- package/dist/{BlogCategoryPage-TN7Y4NBP.js → BlogCategoryPage-KKFQ3PGR.js} +12 -10
- package/dist/BlogCategoryPage-KKFQ3PGR.js.map +1 -0
- package/dist/{BlogPage-TDB3M3PI.js → BlogPage-VLF3NSGE.js} +11 -9
- package/dist/BlogPage-VLF3NSGE.js.map +1 -0
- package/dist/{BlogSinglePage-4A52Q5QW.js → BlogSinglePage-AVK535IW.js} +11 -9
- package/dist/BlogSinglePage-AVK535IW.js.map +1 -0
- package/dist/{BlogTagPage-TOVPBETA.js → BlogTagPage-F3T4D7RH.js} +12 -10
- package/dist/BlogTagPage-F3T4D7RH.js.map +1 -0
- package/dist/{BrandsPage-VUDGYSQI.js → BrandsPage-LVSBZSVC.js} +11 -9
- package/dist/BrandsPage-LVSBZSVC.js.map +1 -0
- package/dist/{Breadcrumb-CI3SN5YR.js → Breadcrumb-VLFTXRFC.js} +11 -9
- package/dist/Breadcrumb-VLFTXRFC.js.map +1 -0
- package/dist/{CartItem-BHLEURAB.js → CartItem-G3H34GW6.js} +11 -9
- package/dist/CartItem-G3H34GW6.js.map +1 -0
- package/dist/{CartSummary-QERVESYN.js → CartSummary-VH37RPUI.js} +11 -9
- package/dist/CartSummary-VH37RPUI.js.map +1 -0
- package/dist/{Copyright-RL6ALKRC.js → Copyright-HE5ZWINZ.js} +3 -3
- package/dist/{Copyright-RL6ALKRC.js.map → Copyright-HE5ZWINZ.js.map} +1 -1
- package/dist/{CustomerLayout-VRZUWGQK.js → CustomerLayout-LKBLOAVW.js} +11 -9
- package/dist/CustomerLayout-LKBLOAVW.js.map +1 -0
- package/dist/{Footer-ZWBZCMDL.js → Footer-HHXAST72.js} +5 -5
- package/dist/{Footer-ZWBZCMDL.js.map → Footer-HHXAST72.js.map} +1 -1
- package/dist/{Header-WNK4O66B.js → Header-GEVZM2YG.js} +8 -6
- package/dist/{Header-WNK4O66B.js.map → Header-GEVZM2YG.js.map} +1 -1
- package/dist/{LoyaltyPage-CGTHWJ76.js → LoyaltyPage-M5CT3CFD.js} +11 -9
- package/dist/LoyaltyPage-M5CT3CFD.js.map +1 -0
- package/dist/{LoyaltyPointsPage-QSU6K3PW.js → LoyaltyPointsPage-SDJB52U6.js} +10 -3
- package/dist/LoyaltyPointsPage-SDJB52U6.js.map +1 -0
- package/dist/{MainMenu-V3T7F32R.js → MainMenu-I7EYS752.js} +3 -3
- package/dist/{MainMenu-V3T7F32R.js.map → MainMenu-I7EYS752.js.map} +1 -1
- package/dist/{MasterLayout-XQF644LD.js → MasterLayout-QDK4T3NE.js} +11 -9
- package/dist/{MasterLayout-XQF644LD.js.map → MasterLayout-QDK4T3NE.js.map} +1 -1
- package/dist/{MobileMenu-U4BVQYKH.js → MobileMenu-7VTCWAVJ.js} +11 -9
- package/dist/MobileMenu-7VTCWAVJ.js.map +1 -0
- package/dist/{NotificationsPage-ZRRQY7TF.js → NotificationsPage-QCABE6QD.js} +11 -9
- package/dist/NotificationsPage-QCABE6QD.js.map +1 -0
- package/dist/{OrderSinglePage-O3XYVMG5.js → OrderSinglePage-2HVBKBG4.js} +11 -9
- package/dist/OrderSinglePage-2HVBKBG4.js.map +1 -0
- package/dist/{OrdersPage-CMXZF5MI.js → OrdersPage-5MNCPCM3.js} +11 -9
- package/dist/OrdersPage-5MNCPCM3.js.map +1 -0
- package/dist/{PageSingle-IIG2US43.js → PageSingle-EN5XIM2B.js} +3 -3
- package/dist/{PageSingle-IIG2US43.js.map → PageSingle-EN5XIM2B.js.map} +1 -1
- package/dist/{ProductDetails-65GDABI7.js → ProductDetails-UEWQPPDZ.js} +13 -6
- package/dist/ProductDetails-UEWQPPDZ.js.map +1 -0
- package/dist/{ProfilePage-SNJ4WXU4.js → ProfilePage-TZSU3GHZ.js} +11 -9
- package/dist/ProfilePage-TZSU3GHZ.js.map +1 -0
- package/dist/{ReviewsPage-UT7UT55J.js → ReviewsPage-WYZ6CHF2.js} +3 -3
- package/dist/{ReviewsPage-UT7UT55J.js.map → ReviewsPage-WYZ6CHF2.js.map} +1 -1
- package/dist/{SeoCartWidget-DBJAXJKT.js → SeoCartWidget-5JR5RXQM.js} +11 -9
- package/dist/SeoCartWidget-5JR5RXQM.js.map +1 -0
- package/dist/{SettingsPage-FLTIOPU5.js → SettingsPage-DIDRQXMM.js} +11 -9
- package/dist/SettingsPage-DIDRQXMM.js.map +1 -0
- package/dist/{StoreClosedNotification-ZT3QKDKS.js → StoreClosedNotification-C2G32E34.js} +12 -10
- package/dist/{StoreClosedNotification-ZT3QKDKS.js.map → StoreClosedNotification-C2G32E34.js.map} +1 -1
- package/dist/{TestimonialsPage-CU7HKU4O.js → TestimonialsPage-XPHFRBZ7.js} +3 -3
- package/dist/{TestimonialsPage-CU7HKU4O.js.map → TestimonialsPage-XPHFRBZ7.js.map} +1 -1
- package/dist/{ThankYouPage-5X6B4X3T.js → ThankYouPage-UA6YYFB2.js} +11 -9
- package/dist/ThankYouPage-UA6YYFB2.js.map +1 -0
- package/dist/{WalletPage-H2BKIDPA.js → WalletPage-AJVV7LVL.js} +11 -9
- package/dist/WalletPage-AJVV7LVL.js.map +1 -0
- package/dist/{WishlistPage-RS5UCIQ3.js → WishlistPage-UW6LJLSW.js} +11 -9
- package/dist/WishlistPage-UW6LJLSW.js.map +1 -0
- package/dist/api/advertisement.d.ts +33 -0
- package/dist/api/advertisement.d.ts.map +1 -0
- package/dist/api/advertisement.js +8 -0
- package/dist/api/advertisement.js.map +1 -0
- package/dist/boot/critical-style.d.ts +3 -0
- package/dist/boot/critical-style.d.ts.map +1 -0
- package/dist/boot/index.d.ts +15 -0
- package/dist/boot/index.d.ts.map +1 -0
- package/dist/boot.d.ts +1 -0
- package/dist/boot.js +69 -0
- package/dist/boot.js.map +1 -0
- package/dist/{chunk-SE5JYWOK.js → chunk-2HCCIHU7.js} +4 -4
- package/dist/{chunk-SE5JYWOK.js.map → chunk-2HCCIHU7.js.map} +1 -1
- package/dist/chunk-4FJGZQD3.js +6 -0
- package/dist/chunk-4FJGZQD3.js.map +1 -0
- package/dist/{chunk-SDFDS4Q3.js → chunk-4WVJJ7SN.js} +20 -4
- package/dist/chunk-4WVJJ7SN.js.map +1 -0
- package/dist/{chunk-NXQJA7D3.js → chunk-BAYGWTNM.js} +3 -3
- package/dist/{chunk-NXQJA7D3.js.map → chunk-BAYGWTNM.js.map} +1 -1
- package/dist/{chunk-OWX2CPNQ.js → chunk-CLBFF2CY.js} +3 -3
- package/dist/{chunk-OWX2CPNQ.js.map → chunk-CLBFF2CY.js.map} +1 -1
- package/dist/chunk-DBW7XXNE.js +103 -0
- package/dist/chunk-DBW7XXNE.js.map +1 -0
- package/dist/{chunk-KHBSGD2D.js → chunk-DBXH3EMJ.js} +3 -3
- package/dist/{chunk-KHBSGD2D.js.map → chunk-DBXH3EMJ.js.map} +1 -1
- package/dist/{chunk-JRNH3FUF.js → chunk-E26YG3AO.js} +4 -4
- package/dist/{chunk-JRNH3FUF.js.map → chunk-E26YG3AO.js.map} +1 -1
- package/dist/{chunk-22SH7OVF.js → chunk-E7WWO3V3.js} +3 -3
- package/dist/{chunk-22SH7OVF.js.map → chunk-E7WWO3V3.js.map} +1 -1
- package/dist/{chunk-UJKFBZFJ.js → chunk-HQ7WA37L.js} +7 -7
- package/dist/{chunk-UJKFBZFJ.js.map → chunk-HQ7WA37L.js.map} +1 -1
- package/dist/chunk-IVBOLREE.js +29 -0
- package/dist/chunk-IVBOLREE.js.map +1 -0
- package/dist/{chunk-6SWDSRIL.js → chunk-KP3VYG4F.js} +6 -6
- package/dist/{chunk-6SWDSRIL.js.map → chunk-KP3VYG4F.js.map} +1 -1
- package/dist/{chunk-4CZRUAZB.js → chunk-PWTXR4RR.js} +10 -10
- package/dist/{chunk-4CZRUAZB.js.map → chunk-PWTXR4RR.js.map} +1 -1
- package/dist/chunk-QJYUTRAQ.js +14 -0
- package/dist/{chunk-7WAXN2PP.js.map → chunk-QJYUTRAQ.js.map} +1 -1
- package/dist/{chunk-JO7JWV34.js → chunk-U55ZJC2X.js} +4 -4
- package/dist/{chunk-JO7JWV34.js.map → chunk-U55ZJC2X.js.map} +1 -1
- package/dist/{chunk-SX5MF2YA.js → chunk-WSDG4ZRI.js} +4 -4
- package/dist/{chunk-SX5MF2YA.js.map → chunk-WSDG4ZRI.js.map} +1 -1
- package/dist/{chunk-NZVR4OF7.js → chunk-X54QP332.js} +8 -8
- package/dist/chunk-X54QP332.js.map +1 -0
- package/dist/components/cart/index.js +1 -1
- package/dist/components/common/index.js +1 -1
- package/dist/components/drawer/index.js +9 -7
- package/dist/components/home/index.js +11 -9
- package/dist/components/layout/PageAdvertisements.d.ts +7 -0
- package/dist/components/layout/PageAdvertisements.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/layout/index.js +8 -1
- package/dist/components/product/index.js +10 -8
- package/dist/components/store/index.js +11 -9
- package/dist/components/store/index.js.map +1 -1
- package/dist/{document-class-67HTP45G.js → document-class-7AXAAWGZ.js} +6 -5
- package/dist/{document-class-67HTP45G.js.map → document-class-7AXAAWGZ.js.map} +1 -1
- package/dist/hooks/index.js +9 -7
- package/dist/hooks/useGtm.js +9 -7
- package/dist/hooks/useMoney.js +9 -7
- package/dist/hooks/useStore.js +9 -7
- package/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/{not-found-QTHBFPNT.js → not-found-3EEMQZMU.js} +11 -9
- package/dist/not-found-3EEMQZMU.js.map +1 -0
- package/dist/providers/index.js +9 -7
- package/dist/routes/account/orders.js +1 -1
- package/dist/routes/account.js +6 -6
- package/dist/routes/blog.js +5 -5
- package/dist/routes/brands.js +1 -1
- package/dist/routes/cart.js +13 -11
- package/dist/routes/cart.js.map +1 -1
- package/dist/routes/home.js +12 -10
- package/dist/routes/loyalty.js +2 -2
- package/dist/routes/page.js +1 -1
- package/dist/routes/product-listing.js +11 -9
- package/dist/routes/product-listing.js.map +1 -1
- package/dist/routes/product.js +11 -9
- package/dist/routes/product.js.map +1 -1
- package/dist/routes/root/beforeLoad.d.ts +2 -2
- package/dist/routes/root/beforeLoad.d.ts.map +1 -1
- package/dist/routes/root/head.d.ts.map +1 -1
- package/dist/routes/testimonials.js +2 -2
- package/dist/routes/thank-you.js +1 -1
- package/dist/skeleton.js +12 -10
- package/dist/tanstack/document-class.d.ts.map +1 -1
- package/dist/tanstack/index.d.ts +1 -0
- package/dist/tanstack/index.d.ts.map +1 -1
- package/dist/tanstack/stylesheet.d.ts +15 -0
- package/dist/tanstack/stylesheet.d.ts.map +1 -0
- package/dist/tanstack.js +34 -5
- package/dist/tanstack.js.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/vite/index.js +1 -1
- package/package.json +12 -3
- package/tailwind/components/Component.tsx.txt +21 -0
- package/tailwind/components/ComponentRegistry.ts.txt +66 -0
- package/tailwind/components/cart/CartItem.tsx.txt +211 -0
- package/tailwind/components/cart/CartSummary.tsx.txt +269 -0
- package/tailwind/components/cart/PreOrderNotification.tsx.txt +16 -0
- package/tailwind/components/cart/PriceQuoteButton.tsx.txt +21 -0
- package/tailwind/components/cart/SeoCartWidget.tsx.txt +99 -0
- package/tailwind/components/cart/index.ts.txt +11 -0
- package/tailwind/components/cart/lazy.ts.txt +15 -0
- package/tailwind/components/collapse/Collapse.tsx.txt +95 -0
- package/tailwind/components/collapse/index.ts.txt +41 -0
- package/tailwind/components/common/Breadcrumb.tsx.txt +27 -0
- package/tailwind/components/common/BreadcrumbJsonLd.tsx.txt +40 -0
- package/tailwind/components/common/BreadcrumbTrail.tsx.txt +97 -0
- package/tailwind/components/common/CurrencySymbol.tsx.txt +8 -0
- package/tailwind/components/common/DeferredData.tsx.txt +122 -0
- package/tailwind/components/common/ErrorPage.tsx.txt +69 -0
- package/tailwind/components/common/Image.tsx.txt +119 -0
- package/tailwind/components/common/Link.tsx.txt +36 -0
- package/tailwind/components/common/Link.types.ts.txt +85 -0
- package/tailwind/components/common/NoContent.tsx.txt +27 -0
- package/tailwind/components/common/RenderWhenVisible.tsx.txt +92 -0
- package/tailwind/components/common/index.ts.txt +42 -0
- package/tailwind/components/common/lazy.ts.txt +28 -0
- package/tailwind/components/defineComponent.ts.txt +32 -0
- package/tailwind/components/drawer/Drawer.tsx.txt +219 -0
- package/tailwind/components/drawer/index.ts.txt +38 -0
- package/tailwind/components/dropdown/Dropdown.tsx.txt +166 -0
- package/tailwind/components/dropdown/index.ts.txt +40 -0
- package/tailwind/components/home/BundleComponent.tsx.txt +35 -0
- package/tailwind/components/home/ComponentErrorBoundary.tsx.txt +98 -0
- package/tailwind/components/home/FeaturedProductsStyle1.tsx.txt +125 -0
- package/tailwind/components/home/FeaturedProductsStyle2.tsx.txt +127 -0
- package/tailwind/components/home/FeaturedProductsStyle3.tsx.txt +71 -0
- package/tailwind/components/home/FixedBanner.tsx.txt +68 -0
- package/tailwind/components/home/FixedProducts.tsx.txt +72 -0
- package/tailwind/components/home/HomePageRenderer.tsx.txt +284 -0
- package/tailwind/components/home/PageBlocks.tsx.txt +24 -0
- package/tailwind/components/home/ParallaxBackground.tsx.txt +50 -0
- package/tailwind/components/home/PhotosSlider.tsx.txt +61 -0
- package/tailwind/components/home/ProductsSlider.tsx.txt +87 -0
- package/tailwind/components/home/ReorderProducts.tsx.txt +38 -0
- package/tailwind/components/home/SquarePhotos.tsx.txt +68 -0
- package/tailwind/components/home/StoreFeatures.tsx.txt +34 -0
- package/tailwind/components/home/Testimonials.tsx.txt +36 -0
- package/tailwind/components/home/Youtube.tsx.txt +48 -0
- package/tailwind/components/home/index.ts.txt +50 -0
- package/tailwind/components/home/lazy.ts.txt +17 -0
- package/tailwind/components/home/register.ts.txt +44 -0
- package/tailwind/components/home/reorder-hooks.ts.txt +52 -0
- package/tailwind/components/home/types.ts.txt +4 -0
- package/tailwind/components/layout/Copyright.tsx.txt +23 -0
- package/tailwind/components/layout/CustomerLayout.tsx.txt +91 -0
- package/tailwind/components/layout/Footer.tsx.txt +343 -0
- package/tailwind/components/layout/Header.tsx.txt +154 -0
- package/tailwind/components/layout/MasterLayout.tsx.txt +63 -0
- package/tailwind/components/layout/PageAdvertisements.tsx.txt +131 -0
- package/tailwind/components/layout/WidgetHead.tsx.txt +53 -0
- package/tailwind/components/layout/index.ts.txt +27 -0
- package/tailwind/components/layout/lazy.ts.txt +25 -0
- package/tailwind/components/layout/types.ts.txt +11 -0
- package/tailwind/components/modal/ImageModal.tsx.txt +70 -0
- package/tailwind/components/modal/Modal.tsx.txt +245 -0
- package/tailwind/components/modal/index.ts.txt +39 -0
- package/tailwind/components/modal/lazy.ts.txt +10 -0
- package/tailwind/components/navigation/MainMenu.tsx.txt +94 -0
- package/tailwind/components/navigation/MobileMenu.tsx.txt +160 -0
- package/tailwind/components/navigation/index.ts.txt +4 -0
- package/tailwind/components/navigation/lazy.ts.txt +11 -0
- package/tailwind/components/product/AddToCartForm.tsx.txt +365 -0
- package/tailwind/components/product/ProductCard.tsx.txt +305 -0
- package/tailwind/components/product/ProductDetails.tsx.txt +267 -0
- package/tailwind/components/product/ProductGallery.tsx.txt +234 -0
- package/tailwind/components/product/index.ts.txt +15 -0
- package/tailwind/components/product/lazy.ts.txt +9 -0
- package/tailwind/components/snippets/AppsSnippets.tsx.txt +225 -0
- package/tailwind/components/snippets/SallaSdkScripts.tsx.txt +14 -0
- package/tailwind/components/snippets/index.ts.txt +4 -0
- package/tailwind/components/store/StoreClosedNotification.tsx.txt +73 -0
- package/tailwind/components/store/index.ts.txt +8 -0
- package/tailwind/components/store/lazy.ts.txt +8 -0
- package/tailwind/components/toast/Toaster.tsx.txt +177 -0
- package/tailwind/components/toast/index.ts.txt +10 -0
- package/tailwind/components/toast/types.ts.txt +38 -0
- package/tailwind/components/toast/useToast.ts.txt +61 -0
- package/tailwind/components/tracking/GoogleTag.tsx.txt +77 -0
- package/tailwind/components/tracking/SiftSnippet.tsx.txt +49 -0
- package/tailwind/components/tracking/TwilightBundles.tsx.txt +67 -0
- package/tailwind/components/tracking/index.ts.txt +11 -0
- package/tailwind/components/tracking/lazy.ts.txt +11 -0
- package/tailwind/components/useComponent.ts.txt +12 -0
- package/tailwind/routes/account/index.tsx.txt +16 -0
- package/tailwind/routes/account/notifications/NotificationsPage.tsx.txt +54 -0
- package/tailwind/routes/account/notifications/index.tsx.txt +39 -0
- package/tailwind/routes/account/notifications/loader.ts.txt +22 -0
- package/tailwind/routes/account/notifications/types.ts.txt +18 -0
- package/tailwind/routes/account/orders/OrderSinglePage.tsx.txt +181 -0
- package/tailwind/routes/account/orders/OrdersPage.tsx.txt +28 -0
- package/tailwind/routes/account/orders/index.tsx.txt +86 -0
- package/tailwind/routes/account/orders/loader.ts.txt +66 -0
- package/tailwind/routes/account/orders/single/OrderActions.tsx.txt +197 -0
- package/tailwind/routes/account/orders/single/OrderHeader.tsx.txt +143 -0
- package/tailwind/routes/account/orders/single/OrderItemRow.tsx.txt +209 -0
- package/tailwind/routes/account/orders/single/OrderSections.tsx.txt +215 -0
- package/tailwind/routes/account/orders/single/types.ts.txt +38 -0
- package/tailwind/routes/account/orders/types.ts.txt +29 -0
- package/tailwind/routes/account/profile/ProfilePage.tsx.txt +30 -0
- package/tailwind/routes/account/profile/index.tsx.txt +39 -0
- package/tailwind/routes/account/profile/loader.ts.txt +16 -0
- package/tailwind/routes/account/profile/types.ts.txt +5 -0
- package/tailwind/routes/account/settings/SettingsPage.tsx.txt +13 -0
- package/tailwind/routes/account/settings/index.tsx.txt +39 -0
- package/tailwind/routes/account/settings/loader.ts.txt +13 -0
- package/tailwind/routes/account/settings/types.ts.txt +5 -0
- package/tailwind/routes/account/types.ts.txt +320 -0
- package/tailwind/routes/account/wallet/WalletPage.tsx.txt +195 -0
- package/tailwind/routes/account/wallet/index.tsx.txt +38 -0
- package/tailwind/routes/account/wallet/loader.ts.txt +27 -0
- package/tailwind/routes/account/wallet/types.ts.txt +11 -0
- package/tailwind/routes/account/wishlist/WishlistPage.tsx.txt +314 -0
- package/tailwind/routes/account/wishlist/WishlistShare.tsx.txt +248 -0
- package/tailwind/routes/account/wishlist/index.tsx.txt +40 -0
- package/tailwind/routes/account/wishlist/loader.ts.txt +26 -0
- package/tailwind/routes/account/wishlist/types.ts.txt +9 -0
- package/tailwind/routes/blog/BlogAuthorPage.tsx.txt +82 -0
- package/tailwind/routes/blog/BlogCategoryPage.tsx.txt +84 -0
- package/tailwind/routes/blog/BlogPage.tsx.txt +191 -0
- package/tailwind/routes/blog/BlogSinglePage.tsx.txt +168 -0
- package/tailwind/routes/blog/BlogTagPage.tsx.txt +84 -0
- package/tailwind/routes/blog/BlogTags.tsx.txt +54 -0
- package/tailwind/routes/blog/components.tsx.txt +87 -0
- package/tailwind/routes/blog/index.tsx.txt +238 -0
- package/tailwind/routes/blog/loader.ts.txt +173 -0
- package/tailwind/routes/blog/types.ts.txt +106 -0
- package/tailwind/routes/brands/BrandsPage.tsx.txt +179 -0
- package/tailwind/routes/brands/index.tsx.txt +47 -0
- package/tailwind/routes/brands/loader.ts.txt +14 -0
- package/tailwind/routes/brands/types.ts.txt +53 -0
- package/tailwind/routes/cart/CartPage.tsx.txt +112 -0
- package/tailwind/routes/cart/index.tsx.txt +39 -0
- package/tailwind/routes/cart/loader.ts.txt +15 -0
- package/tailwind/routes/cart/types.ts.txt +6 -0
- package/tailwind/routes/home/HomePage.tsx.txt +53 -0
- package/tailwind/routes/home/index.tsx.txt +56 -0
- package/tailwind/routes/home/loader.ts.txt +24 -0
- package/tailwind/routes/home/types.ts.txt +11 -0
- package/tailwind/routes/index.ts.txt +94 -0
- package/tailwind/routes/lazy.ts.txt +105 -0
- package/tailwind/routes/loyalty/LoyaltyPage.tsx.txt +17 -0
- package/tailwind/routes/loyalty/LoyaltyPointsPage.tsx.txt +11 -0
- package/tailwind/routes/loyalty/index.tsx.txt +81 -0
- package/tailwind/routes/loyalty/loader.ts.txt +53 -0
- package/tailwind/routes/loyalty/types.ts.txt +109 -0
- package/tailwind/routes/not-found.tsx.txt +50 -0
- package/tailwind/routes/page/PageSingle.tsx.txt +34 -0
- package/tailwind/routes/page/index.tsx.txt +60 -0
- package/tailwind/routes/page/loader.ts.txt +19 -0
- package/tailwind/routes/page/types.ts.txt +34 -0
- package/tailwind/routes/product/ProductPage.tsx.txt +77 -0
- package/tailwind/routes/product/index.tsx.txt +157 -0
- package/tailwind/routes/product/loader.ts.txt +35 -0
- package/tailwind/routes/product/types.ts.txt +6 -0
- package/tailwind/routes/product-listing/ProductListPage.tsx.txt +321 -0
- package/tailwind/routes/product-listing/index.tsx.txt +95 -0
- package/tailwind/routes/product-listing/loader.ts.txt +323 -0
- package/tailwind/routes/product-listing/types.ts.txt +96 -0
- package/tailwind/routes/product-listing/useAppliedFilters.ts.txt +130 -0
- package/tailwind/routes/product-short-link.ts.txt +104 -0
- package/tailwind/routes/root/beforeLoad.ts.txt +354 -0
- package/tailwind/routes/root/head.ts.txt +424 -0
- package/tailwind/routes/root/index.ts.txt +2 -0
- package/tailwind/routes/seo-redirects.ts.txt +82 -0
- package/tailwind/routes/testimonials/ReviewsPage.tsx.txt +12 -0
- package/tailwind/routes/testimonials/TestimonialsPage.tsx.txt +80 -0
- package/tailwind/routes/testimonials/index.tsx.txt +80 -0
- package/tailwind/routes/testimonials/loader.ts.txt +29 -0
- package/tailwind/routes/testimonials/types.ts.txt +9 -0
- package/tailwind/routes/thank-you/ThankYouPage.tsx.txt +241 -0
- package/tailwind/routes/thank-you/index.tsx.txt +49 -0
- package/tailwind/routes/thank-you/loader.ts.txt +40 -0
- package/tailwind/routes/thank-you/types.ts.txt +16 -0
- package/tailwind/routes/types.ts.txt +41 -0
- package/dist/BlogAuthorPage-GTF4UGSS.js.map +0 -1
- package/dist/BlogCategoryPage-TN7Y4NBP.js.map +0 -1
- package/dist/BlogPage-TDB3M3PI.js.map +0 -1
- package/dist/BlogSinglePage-4A52Q5QW.js.map +0 -1
- package/dist/BlogTagPage-TOVPBETA.js.map +0 -1
- package/dist/BrandsPage-VUDGYSQI.js.map +0 -1
- package/dist/Breadcrumb-CI3SN5YR.js.map +0 -1
- package/dist/CartItem-BHLEURAB.js.map +0 -1
- package/dist/CartSummary-QERVESYN.js.map +0 -1
- package/dist/CustomerLayout-VRZUWGQK.js.map +0 -1
- package/dist/LoyaltyPage-CGTHWJ76.js.map +0 -1
- package/dist/LoyaltyPointsPage-QSU6K3PW.js.map +0 -1
- package/dist/MobileMenu-U4BVQYKH.js.map +0 -1
- package/dist/NotificationsPage-ZRRQY7TF.js.map +0 -1
- package/dist/OrderSinglePage-O3XYVMG5.js.map +0 -1
- package/dist/OrdersPage-CMXZF5MI.js.map +0 -1
- package/dist/ProductDetails-65GDABI7.js.map +0 -1
- package/dist/ProfilePage-SNJ4WXU4.js.map +0 -1
- package/dist/SeoCartWidget-DBJAXJKT.js.map +0 -1
- package/dist/SettingsPage-FLTIOPU5.js.map +0 -1
- package/dist/ThankYouPage-5X6B4X3T.js.map +0 -1
- package/dist/WalletPage-H2BKIDPA.js.map +0 -1
- package/dist/WishlistPage-RS5UCIQ3.js.map +0 -1
- package/dist/chunk-7WAXN2PP.js +0 -14
- package/dist/chunk-NZVR4OF7.js.map +0 -1
- package/dist/chunk-SDFDS4Q3.js.map +0 -1
- package/dist/not-found-QTHBFPNT.js.map +0 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { memo, useState, useEffect } from 'react';
|
|
2
|
+
import type { Breadcrumb } from '../../types';
|
|
3
|
+
import { Link } from './Link';
|
|
4
|
+
|
|
5
|
+
const MAX_VISIBLE_ITEMS = 4;
|
|
6
|
+
|
|
7
|
+
interface BreadcrumbTrailProps {
|
|
8
|
+
items: Breadcrumb[];
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ChevronIcon = () => (
|
|
13
|
+
<svg
|
|
14
|
+
version="1.1"
|
|
15
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
16
|
+
width="32"
|
|
17
|
+
height="32"
|
|
18
|
+
viewBox="0 0 32 32"
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
>
|
|
21
|
+
<title>keyboard_arrow_left</title>
|
|
22
|
+
<path d="M20.563 22.104l-1.875 1.875-8-8 8-8 1.875 1.875-6.125 6.125z"></path>
|
|
23
|
+
</svg>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
function truncateItems(items: Breadcrumb[]): Breadcrumb[] {
|
|
27
|
+
if (items.length <= MAX_VISIBLE_ITEMS) return items;
|
|
28
|
+
|
|
29
|
+
return [items[0], { name: '…', url: '' }, ...items.slice(-2)];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const BreadcrumbTrail = memo(function BreadcrumbTrail({
|
|
33
|
+
items,
|
|
34
|
+
className,
|
|
35
|
+
}: BreadcrumbTrailProps) {
|
|
36
|
+
const [expanded, setExpanded] = useState(false);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
setExpanded(false);
|
|
40
|
+
}, [items]);
|
|
41
|
+
|
|
42
|
+
if (items.length === 0) return null;
|
|
43
|
+
|
|
44
|
+
const displayItems = expanded ? items : truncateItems(items);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<nav className={className ?? 'breadcrumbs w-full py-5'} aria-label="Breadcrumb">
|
|
48
|
+
<ol className="s-breadcrumb-wrapper" itemScope itemType="https://schema.org/BreadcrumbList">
|
|
49
|
+
{displayItems.map((item, index) => {
|
|
50
|
+
const isLast = index === displayItems.length - 1;
|
|
51
|
+
const isEllipsis = item.name === '…' && !item.url;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<li
|
|
55
|
+
key={`${item.url}-${item.name}`}
|
|
56
|
+
className={['s-breadcrumb-item', isEllipsis ? 'cursor-pointer select-none' : ''].join(
|
|
57
|
+
' '
|
|
58
|
+
)}
|
|
59
|
+
aria-current={isLast ? 'page' : undefined}
|
|
60
|
+
itemProp="itemListElement"
|
|
61
|
+
itemScope
|
|
62
|
+
itemType="https://schema.org/ListItem"
|
|
63
|
+
>
|
|
64
|
+
{isEllipsis ? (
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
onClick={() => {
|
|
68
|
+
setExpanded(true);
|
|
69
|
+
}}
|
|
70
|
+
className="inline-flex items-center gap-1 hover:underline"
|
|
71
|
+
aria-label="Show all breadcrumbs"
|
|
72
|
+
>
|
|
73
|
+
<span aria-hidden="true" className="text-xs">
|
|
74
|
+
•••
|
|
75
|
+
</span>
|
|
76
|
+
</button>
|
|
77
|
+
) : isLast ? (
|
|
78
|
+
<span itemProp="name">{item.name}</span>
|
|
79
|
+
) : (
|
|
80
|
+
<>
|
|
81
|
+
<Link to={item.url} itemProp="item">
|
|
82
|
+
<span itemProp="name">{item.name}</span>
|
|
83
|
+
</Link>
|
|
84
|
+
<span aria-hidden="true" className="s-breadcrumb-arrow inline-flex">
|
|
85
|
+
<ChevronIcon />
|
|
86
|
+
</span>
|
|
87
|
+
<meta itemProp="position" content={String(index + 1)} />
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
90
|
+
{isLast && <meta itemProp="position" content={String(index + 1)} />}
|
|
91
|
+
</li>
|
|
92
|
+
);
|
|
93
|
+
})}
|
|
94
|
+
</ol>
|
|
95
|
+
</nav>
|
|
96
|
+
);
|
|
97
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a currency symbol.
|
|
3
|
+
* SAR uses the dedicated `sicon-sar` icon; all other currencies render as text.
|
|
4
|
+
*/
|
|
5
|
+
export function CurrencySymbol({ currency }: { currency: string }) {
|
|
6
|
+
if (currency === 'SAR') return <i className="sicon-sar" aria-hidden="true" />;
|
|
7
|
+
return <span>{currency}</span>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Suspense, memo, use, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface DeferredDataProps<T> {
|
|
4
|
+
promise: Promise<T> | T;
|
|
5
|
+
fallback: ReactNode;
|
|
6
|
+
children: (data: T) => ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const DEFERRED_KEY = Symbol.for('twilight.deferred');
|
|
10
|
+
|
|
11
|
+
interface DeferredState<T> {
|
|
12
|
+
status: 'pending' | 'success' | 'error';
|
|
13
|
+
value?: T;
|
|
14
|
+
error?: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DeferredPromise<T> extends Promise<T> {
|
|
18
|
+
[DEFERRED_KEY]?: DeferredState<T>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const seenPromises = typeof WeakSet !== 'undefined' ? new WeakSet() : null;
|
|
22
|
+
|
|
23
|
+
function defer<T>(promise: Promise<T>): DeferredPromise<T> {
|
|
24
|
+
const existing = (promise as DeferredPromise<T>)[DEFERRED_KEY];
|
|
25
|
+
if (existing) return promise as DeferredPromise<T>;
|
|
26
|
+
|
|
27
|
+
const state: DeferredState<T> = { status: 'pending' };
|
|
28
|
+
|
|
29
|
+
const deferred = promise as DeferredPromise<T>;
|
|
30
|
+
deferred[DEFERRED_KEY] = state;
|
|
31
|
+
|
|
32
|
+
promise.then(
|
|
33
|
+
(value) => {
|
|
34
|
+
state.status = 'success';
|
|
35
|
+
state.value = value;
|
|
36
|
+
},
|
|
37
|
+
(error) => {
|
|
38
|
+
state.status = 'error';
|
|
39
|
+
state.error = error;
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return deferred;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const DeferredRenderer = memo(function DeferredRenderer<T>({
|
|
47
|
+
promise,
|
|
48
|
+
children,
|
|
49
|
+
}: {
|
|
50
|
+
promise: DeferredPromise<T>;
|
|
51
|
+
children: (data: T) => ReactNode;
|
|
52
|
+
}) {
|
|
53
|
+
const state = promise[DEFERRED_KEY];
|
|
54
|
+
|
|
55
|
+
if (state?.status === 'success') {
|
|
56
|
+
return children(state.value!);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (state?.status === 'error') {
|
|
60
|
+
throw state.error;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const data = use(promise);
|
|
64
|
+
return children(data);
|
|
65
|
+
}) as <T>(props: { promise: DeferredPromise<T>; children: (data: T) => ReactNode }) => ReactNode;
|
|
66
|
+
|
|
67
|
+
function DeferredFallback<T>({
|
|
68
|
+
promise,
|
|
69
|
+
fallback,
|
|
70
|
+
children,
|
|
71
|
+
}: {
|
|
72
|
+
promise: DeferredPromise<T>;
|
|
73
|
+
fallback: ReactNode;
|
|
74
|
+
children: (data: T) => ReactNode;
|
|
75
|
+
}) {
|
|
76
|
+
const state = promise[DEFERRED_KEY];
|
|
77
|
+
if (state?.status === 'success') {
|
|
78
|
+
return children(state.value!);
|
|
79
|
+
}
|
|
80
|
+
return <>{fallback}</>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function resolveDeferred<T>(
|
|
84
|
+
promiseOrValue: Promise<T> | T | undefined
|
|
85
|
+
): T | Promise<T> | undefined {
|
|
86
|
+
if (promiseOrValue == null || !(promiseOrValue instanceof Promise)) return promiseOrValue;
|
|
87
|
+
const deferred = defer(promiseOrValue);
|
|
88
|
+
const state = deferred[DEFERRED_KEY];
|
|
89
|
+
if (state?.status === 'success') return state.value as T;
|
|
90
|
+
return promiseOrValue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function DeferredData<T>({ promise, fallback, children }: DeferredDataProps<T>) {
|
|
94
|
+
if (!(promise instanceof Promise)) {
|
|
95
|
+
return children(promise);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (seenPromises && promise instanceof Promise) seenPromises.add(promise);
|
|
99
|
+
|
|
100
|
+
const deferred = defer(promise);
|
|
101
|
+
const state = deferred[DEFERRED_KEY];
|
|
102
|
+
|
|
103
|
+
if (state?.status === 'success') {
|
|
104
|
+
return children(state.value!);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (state?.status === 'error') {
|
|
108
|
+
throw state.error;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Suspense
|
|
113
|
+
fallback={
|
|
114
|
+
<DeferredFallback promise={deferred} fallback={fallback}>
|
|
115
|
+
{children}
|
|
116
|
+
</DeferredFallback>
|
|
117
|
+
}
|
|
118
|
+
>
|
|
119
|
+
<DeferredRenderer promise={deferred}>{children}</DeferredRenderer>
|
|
120
|
+
</Suspense>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import { useTranslation } from '../../providers/I18nProvider';
|
|
3
|
+
|
|
4
|
+
const LINK_STYLE: React.CSSProperties = {
|
|
5
|
+
backgroundColor: 'var(--color-primary)',
|
|
6
|
+
color: 'var(--color-primary-reverse, #ffffff)',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export interface ErrorPageProps {
|
|
10
|
+
code?: string | number;
|
|
11
|
+
title?: string;
|
|
12
|
+
message?: string;
|
|
13
|
+
actionText?: string;
|
|
14
|
+
actionHref?: string;
|
|
15
|
+
showAction?: boolean;
|
|
16
|
+
LinkComponent?: ComponentType<{
|
|
17
|
+
to: string;
|
|
18
|
+
className?: string;
|
|
19
|
+
style?: React.CSSProperties;
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}>;
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ErrorPage({
|
|
26
|
+
code,
|
|
27
|
+
title,
|
|
28
|
+
message,
|
|
29
|
+
actionText,
|
|
30
|
+
actionHref = '/',
|
|
31
|
+
showAction = true,
|
|
32
|
+
LinkComponent,
|
|
33
|
+
children,
|
|
34
|
+
}: ErrorPageProps) {
|
|
35
|
+
const { t } = useTranslation();
|
|
36
|
+
|
|
37
|
+
const resolvedTitle = title ?? t(`common.errors.${code}`) ?? 'Error';
|
|
38
|
+
const resolvedMessage =
|
|
39
|
+
message ?? t('common.errors.error_occurred') ?? 'An unexpected error occurred.';
|
|
40
|
+
const resolvedActionText = actionText ?? t('common.elements.back_home') ?? 'Back to Home';
|
|
41
|
+
|
|
42
|
+
const linkClassName =
|
|
43
|
+
'inline-flex items-center px-6 py-3 rounded-lg transition-opacity hover:opacity-90';
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="container py-16 text-center">
|
|
47
|
+
{code && (
|
|
48
|
+
<h1 className="text-6xl font-bold text-gray-300 mb-4" data-testid="error-code">
|
|
49
|
+
{code}
|
|
50
|
+
</h1>
|
|
51
|
+
)}
|
|
52
|
+
<h2 className="text-2xl font-semibold text-gray-700 mb-4">{resolvedTitle}</h2>
|
|
53
|
+
<p className="text-gray-500 mb-8">{resolvedMessage}</p>
|
|
54
|
+
|
|
55
|
+
{children}
|
|
56
|
+
|
|
57
|
+
{showAction &&
|
|
58
|
+
(LinkComponent ? (
|
|
59
|
+
<LinkComponent to={actionHref} className={linkClassName} style={LINK_STYLE}>
|
|
60
|
+
{resolvedActionText}
|
|
61
|
+
</LinkComponent>
|
|
62
|
+
) : (
|
|
63
|
+
<a href={actionHref} className={linkClassName} style={LINK_STYLE}>
|
|
64
|
+
{resolvedActionText}
|
|
65
|
+
</a>
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import { getCdnImageSrcSet, getCdnImageUrl } from '../../utils/cdn-image';
|
|
3
|
+
|
|
4
|
+
export interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
|
5
|
+
/** Image source URL */
|
|
6
|
+
src?: string;
|
|
7
|
+
/** Alt text for accessibility */
|
|
8
|
+
alt: string;
|
|
9
|
+
/** Whether this image is above the fold / LCP candidate */
|
|
10
|
+
priority?: boolean;
|
|
11
|
+
/** Aspect ratio for CLS prevention (e.g. "16/9", "1/1", "4/3") */
|
|
12
|
+
aspectRatio?: string;
|
|
13
|
+
/** Object fit mode */
|
|
14
|
+
objectFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
|
|
15
|
+
/** Mobile image source for <picture> element */
|
|
16
|
+
mobileSrc?: string;
|
|
17
|
+
/** Mobile breakpoint for <source> media query (default: 768px) */
|
|
18
|
+
mobileBreakpoint?: number;
|
|
19
|
+
/** Skip the wrapper div */
|
|
20
|
+
noWrapper?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* CDN widths for a density `srcset`. Pass `sizes` so the browser picks a
|
|
23
|
+
* candidate close to the displayed CSS size.
|
|
24
|
+
*/
|
|
25
|
+
srcSetWidths?: readonly number[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Optimized Image component with native browser lazy loading.
|
|
30
|
+
*
|
|
31
|
+
* - Uses `loading="lazy"` + `decoding="async"` by default for below-the-fold images.
|
|
32
|
+
* - Uses `loading="eager"` + `fetchPriority="high"` for priority (above-the-fold / LCP) images.
|
|
33
|
+
* - Supports responsive `<picture>` with mobile source.
|
|
34
|
+
* - Provides `aspectRatio` for CLS prevention.
|
|
35
|
+
* - When `srcSetWidths` + `sizes` are set, emits a sized CDN `srcset`.
|
|
36
|
+
*/
|
|
37
|
+
export const Image = memo(function Image({
|
|
38
|
+
src,
|
|
39
|
+
alt,
|
|
40
|
+
priority = false,
|
|
41
|
+
aspectRatio,
|
|
42
|
+
objectFit = 'cover',
|
|
43
|
+
mobileSrc,
|
|
44
|
+
mobileBreakpoint = 768,
|
|
45
|
+
noWrapper = false,
|
|
46
|
+
className = '',
|
|
47
|
+
style,
|
|
48
|
+
width,
|
|
49
|
+
height,
|
|
50
|
+
sizes,
|
|
51
|
+
srcSet: srcSetProp,
|
|
52
|
+
srcSetWidths,
|
|
53
|
+
...rest
|
|
54
|
+
}: ImageProps) {
|
|
55
|
+
if (!src) return null;
|
|
56
|
+
|
|
57
|
+
const generatedSrcSet =
|
|
58
|
+
srcSetProp || (srcSetWidths?.length ? getCdnImageSrcSet(src, srcSetWidths) : undefined);
|
|
59
|
+
const generatedMobileSrcSet =
|
|
60
|
+
srcSetWidths?.length && mobileSrc ? getCdnImageSrcSet(mobileSrc, srcSetWidths) : undefined;
|
|
61
|
+
const cdnWidth = srcSetWidths?.length
|
|
62
|
+
? width != null && srcSetWidths.includes(Number(width))
|
|
63
|
+
? Number(width)
|
|
64
|
+
: srcSetWidths[0]
|
|
65
|
+
: width;
|
|
66
|
+
// Width-only CDN URLs so `src` matches a srcset candidate for preload.
|
|
67
|
+
const cdnOptions = generatedSrcSet ? { width: cdnWidth } : { width: cdnWidth, height };
|
|
68
|
+
|
|
69
|
+
const optimizedSrc = getCdnImageUrl(src, cdnOptions);
|
|
70
|
+
const optimizedMobileSrc = getCdnImageUrl(mobileSrc, cdnOptions);
|
|
71
|
+
|
|
72
|
+
const objectFitClass = `object-${objectFit}`;
|
|
73
|
+
const imgClasses = [className, objectFitClass].filter(Boolean).join(' ');
|
|
74
|
+
|
|
75
|
+
const loadingProps: React.ImgHTMLAttributes<HTMLImageElement> = priority
|
|
76
|
+
? { loading: 'eager' as const, fetchPriority: 'high' as const, decoding: 'sync' as const }
|
|
77
|
+
: { loading: 'lazy' as const, decoding: 'async' as const };
|
|
78
|
+
|
|
79
|
+
const hasWrapper = !noWrapper && !!aspectRatio;
|
|
80
|
+
|
|
81
|
+
const img = (
|
|
82
|
+
<img
|
|
83
|
+
src={optimizedSrc}
|
|
84
|
+
alt={alt}
|
|
85
|
+
className={imgClasses}
|
|
86
|
+
width={width}
|
|
87
|
+
height={height}
|
|
88
|
+
sizes={sizes}
|
|
89
|
+
srcSet={generatedSrcSet}
|
|
90
|
+
// With a wrapper, `style` goes to the wrapper; otherwise it must land on the <img>.
|
|
91
|
+
style={hasWrapper ? undefined : style}
|
|
92
|
+
{...loadingProps}
|
|
93
|
+
{...rest}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const imgElement = optimizedMobileSrc ? (
|
|
98
|
+
<picture>
|
|
99
|
+
<source
|
|
100
|
+
media={`(max-width: ${mobileBreakpoint}px)`}
|
|
101
|
+
srcSet={generatedMobileSrcSet || optimizedMobileSrc}
|
|
102
|
+
sizes={generatedMobileSrcSet ? sizes : undefined}
|
|
103
|
+
/>
|
|
104
|
+
{img}
|
|
105
|
+
</picture>
|
|
106
|
+
) : (
|
|
107
|
+
img
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (!hasWrapper) {
|
|
111
|
+
return imgElement;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div className="relative overflow-hidden" style={{ aspectRatio, ...style }}>
|
|
116
|
+
{imgElement}
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { useLinkAdapter } from '../../providers/LinkProvider';
|
|
3
|
+
import type { BaseLinkProps } from './Link.types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Framework-agnostic Link component.
|
|
7
|
+
*
|
|
8
|
+
* Uses the LinkProvider context to render the framework-specific Link adapter.
|
|
9
|
+
* Works with TanStack Router, Next.js, or any configured adapter.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* // Basic usage
|
|
14
|
+
* <Link to="/products">View Products</Link>
|
|
15
|
+
*
|
|
16
|
+
* // With preloading
|
|
17
|
+
* <Link to="/product/123" preload="intent">Product Details</Link>
|
|
18
|
+
*
|
|
19
|
+
* // With styling
|
|
20
|
+
* <Link to="/cart" className="nav-link" style={{ fontWeight: 'bold' }}>Cart</Link>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const Link = forwardRef<HTMLAnchorElement, BaseLinkProps>(
|
|
24
|
+
({ to, href, children, ...props }, ref) => {
|
|
25
|
+
const Adapter = useLinkAdapter();
|
|
26
|
+
const destination = to ?? href ?? '/';
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Adapter ref={ref} to={destination} {...props} data-spa-link="">
|
|
30
|
+
{children}
|
|
31
|
+
</Adapter>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
Link.displayName = 'Link';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ReactNode, CSSProperties, MouseEventHandler, Ref } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Preload behavior for Link component
|
|
5
|
+
*/
|
|
6
|
+
export type LinkPreload = 'intent' | 'render' | false;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Framework-agnostic Link props.
|
|
10
|
+
* Supported across TanStack Router, Next.js, and other React frameworks.
|
|
11
|
+
*/
|
|
12
|
+
export interface BaseLinkProps {
|
|
13
|
+
/**
|
|
14
|
+
* The destination path
|
|
15
|
+
*/
|
|
16
|
+
to?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Alternative to `to` - supported for compatibility
|
|
19
|
+
*/
|
|
20
|
+
href?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Children elements
|
|
23
|
+
*/
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* Optional className for styling
|
|
27
|
+
*/
|
|
28
|
+
className?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Inline styles
|
|
31
|
+
*/
|
|
32
|
+
style?: CSSProperties;
|
|
33
|
+
/**
|
|
34
|
+
* Optional title attribute
|
|
35
|
+
*/
|
|
36
|
+
title?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Target attribute for the anchor
|
|
39
|
+
*/
|
|
40
|
+
target?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Rel attribute for the anchor
|
|
43
|
+
*/
|
|
44
|
+
rel?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Click handler
|
|
47
|
+
*/
|
|
48
|
+
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
|
49
|
+
/**
|
|
50
|
+
* Preload behavior:
|
|
51
|
+
* - 'intent': Preload on hover/focus (default for TanStack)
|
|
52
|
+
* - 'render': Preload when link renders
|
|
53
|
+
* - false: No preloading
|
|
54
|
+
*/
|
|
55
|
+
preload?: LinkPreload;
|
|
56
|
+
/**
|
|
57
|
+
* Replace current history entry instead of pushing
|
|
58
|
+
*/
|
|
59
|
+
replace?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* ARIA label for accessibility
|
|
62
|
+
*/
|
|
63
|
+
'aria-label'?: string;
|
|
64
|
+
/**
|
|
65
|
+
* HTML microdata itemProp attribute
|
|
66
|
+
*/
|
|
67
|
+
itemProp?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Ref forwarding
|
|
70
|
+
*/
|
|
71
|
+
ref?: Ref<HTMLAnchorElement>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Link adapter component type
|
|
76
|
+
*/
|
|
77
|
+
export type LinkAdapter = React.ComponentType<BaseLinkProps>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Props for LinkProvider
|
|
81
|
+
*/
|
|
82
|
+
export interface LinkProviderProps {
|
|
83
|
+
adapter: LinkAdapter;
|
|
84
|
+
children: ReactNode;
|
|
85
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Link } from './Link';
|
|
2
|
+
|
|
3
|
+
interface NoContentProps {
|
|
4
|
+
icon?: string;
|
|
5
|
+
message: string;
|
|
6
|
+
actionLabel?: string;
|
|
7
|
+
actionUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function NoContent({
|
|
11
|
+
icon = 'sicon-shopping-bag',
|
|
12
|
+
message,
|
|
13
|
+
actionLabel,
|
|
14
|
+
actionUrl = '/',
|
|
15
|
+
}: NoContentProps) {
|
|
16
|
+
return (
|
|
17
|
+
<div className="no-content-placeholder">
|
|
18
|
+
<i className={`${icon} icon`}></i>
|
|
19
|
+
<p>{message}</p>
|
|
20
|
+
{actionLabel && (
|
|
21
|
+
<Link to={actionUrl} className="btn btn--outline-primary btn--rounded-full">
|
|
22
|
+
{actionLabel}
|
|
23
|
+
</Link>
|
|
24
|
+
)}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { memo, useState, useEffect, useRef, Suspense, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
function DefaultSkeleton({ height }: { height: string }) {
|
|
4
|
+
return <div style={{ minHeight: height }} className="animate-pulse rounded bg-gray-100" />;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Sibling of the children inside Suspense: its effect only runs once the boundary resolves. */
|
|
8
|
+
function MarkMounted({ onMount }: { onMount: (mounted: boolean) => void }) {
|
|
9
|
+
useEffect(() => onMount(true), [onMount]);
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RenderWhenVisibleProps {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
rootMargin?: string;
|
|
16
|
+
placeholder?: ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
id?: string;
|
|
19
|
+
estimatedHeight?: string;
|
|
20
|
+
renderOnMount?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* RenderWhenVisible — defers mounting children until the container enters the viewport.
|
|
25
|
+
*
|
|
26
|
+
* Children are NOT in the React tree at all until the Intersection Observer fires,
|
|
27
|
+
* so no effects, API calls, or renders happen for off-screen sections.
|
|
28
|
+
* Once rendered, children stay mounted permanently (no re-unmount on scroll away).
|
|
29
|
+
*
|
|
30
|
+
* The `estimatedHeight` prop prevents the zero-height collapse problem: without it
|
|
31
|
+
* every unrendered section stacks at the top with 0px height, causing all observers
|
|
32
|
+
* to fire at once on mount. It applies as a min-height until the children have
|
|
33
|
+
* actually committed — through the Suspense window too, since a custom placeholder
|
|
34
|
+
* may be shorter than the estimate. After that the section's height is owned by its
|
|
35
|
+
* content, so shorter blocks don't leave reserved dead space below them.
|
|
36
|
+
*/
|
|
37
|
+
export const RenderWhenVisible = memo(function RenderWhenVisible({
|
|
38
|
+
children,
|
|
39
|
+
rootMargin = '50px',
|
|
40
|
+
placeholder,
|
|
41
|
+
className,
|
|
42
|
+
id,
|
|
43
|
+
estimatedHeight = '400px',
|
|
44
|
+
renderOnMount = false,
|
|
45
|
+
}: RenderWhenVisibleProps) {
|
|
46
|
+
const ref = useRef<HTMLElement>(null);
|
|
47
|
+
const [visible, setVisible] = useState(renderOnMount);
|
|
48
|
+
const [mounted, setMounted] = useState(false);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (renderOnMount || visible) return;
|
|
52
|
+
const el = ref.current;
|
|
53
|
+
if (!el) return;
|
|
54
|
+
|
|
55
|
+
const observer = new IntersectionObserver(
|
|
56
|
+
(entries) => {
|
|
57
|
+
// A busy main thread (e.g. mounting a page while the user is already
|
|
58
|
+
// scrolling) batches entries; reading only the first drops a
|
|
59
|
+
// [false, true] pair and the observer never fires again.
|
|
60
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
61
|
+
setVisible(true);
|
|
62
|
+
observer.disconnect();
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{ rootMargin }
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
observer.observe(el);
|
|
69
|
+
|
|
70
|
+
return () => observer.disconnect();
|
|
71
|
+
}, [renderOnMount, visible, rootMargin]);
|
|
72
|
+
|
|
73
|
+
const fallback = placeholder ?? <DefaultSkeleton height={estimatedHeight} />;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<section
|
|
77
|
+
ref={!visible ? ref : undefined}
|
|
78
|
+
className={className}
|
|
79
|
+
id={id}
|
|
80
|
+
style={{ minHeight: mounted ? undefined : estimatedHeight }}
|
|
81
|
+
>
|
|
82
|
+
{visible ? (
|
|
83
|
+
<Suspense fallback={fallback}>
|
|
84
|
+
{children}
|
|
85
|
+
<MarkMounted onMount={setMounted} />
|
|
86
|
+
</Suspense>
|
|
87
|
+
) : (
|
|
88
|
+
fallback
|
|
89
|
+
)}
|
|
90
|
+
</section>
|
|
91
|
+
);
|
|
92
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LazyBreadcrumb,
|
|
3
|
+
LazyNoContent,
|
|
4
|
+
LazyRenderWhenVisible,
|
|
5
|
+
LazyCurrencySymbol,
|
|
6
|
+
LazyErrorPage,
|
|
7
|
+
} from './lazy';
|
|
8
|
+
import { Image } from './Image';
|
|
9
|
+
import { Link } from './Link';
|
|
10
|
+
|
|
11
|
+
// Lazy-loaded common components (code-split)
|
|
12
|
+
export {
|
|
13
|
+
LazyBreadcrumb as Breadcrumb,
|
|
14
|
+
LazyNoContent as NoContent,
|
|
15
|
+
LazyRenderWhenVisible as RenderWhenVisible,
|
|
16
|
+
LazyCurrencySymbol as CurrencySymbol,
|
|
17
|
+
LazyErrorPage as ErrorPage,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Eager components (statically imported in multiple places)
|
|
21
|
+
export { Image, Link };
|
|
22
|
+
|
|
23
|
+
// Skeleton components — sourced from @salla.sa/twilight-components-react (kept eager for instant render during loading)
|
|
24
|
+
export {
|
|
25
|
+
PageSkeleton,
|
|
26
|
+
ProductDetailSkeleton,
|
|
27
|
+
BlogSkeleton,
|
|
28
|
+
BreadcrumbSkeleton,
|
|
29
|
+
ProductCardSkeleton,
|
|
30
|
+
ProductGridSkeleton,
|
|
31
|
+
SkeletonPulse,
|
|
32
|
+
CustomerPageSkeleton,
|
|
33
|
+
CustomerLayoutSkeleton,
|
|
34
|
+
} from '@salla.sa/twilight-components-react';
|
|
35
|
+
export type { PageSkeletonProps } from '@salla.sa/twilight-components-react';
|
|
36
|
+
|
|
37
|
+
// DeferredData - React 19 Suspense + use() pattern (eager, lightweight)
|
|
38
|
+
export { DeferredData, resolveDeferred } from './DeferredData';
|
|
39
|
+
export type { DeferredDataProps } from './DeferredData';
|
|
40
|
+
|
|
41
|
+
// Shared types (exported for external use)
|
|
42
|
+
export type { BaseLinkProps as LinkProps, LinkPreload } from './Link.types';
|