@salla.sa/twilight-theme-engine 1.0.85 → 1.0.87

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.
Files changed (390) hide show
  1. package/README.md +4 -3
  2. package/bin/twilight-check-manifest.mjs +16 -0
  3. package/dist/{AddToCartForm-B5C2M4OZ.js → AddToCartForm-P644O2QL.js} +4 -4
  4. package/dist/{AddToCartForm-B5C2M4OZ.js.map → AddToCartForm-P644O2QL.js.map} +1 -1
  5. package/dist/{BlogAuthorPage-GTF4UGSS.js → BlogAuthorPage-VWSO6MPY.js} +13 -11
  6. package/dist/BlogAuthorPage-VWSO6MPY.js.map +1 -0
  7. package/dist/{BlogCategoryPage-TN7Y4NBP.js → BlogCategoryPage-K2KSQ6T6.js} +13 -11
  8. package/dist/BlogCategoryPage-K2KSQ6T6.js.map +1 -0
  9. package/dist/{BlogPage-TDB3M3PI.js → BlogPage-XRM7VWOZ.js} +12 -10
  10. package/dist/BlogPage-XRM7VWOZ.js.map +1 -0
  11. package/dist/{BlogSinglePage-4A52Q5QW.js → BlogSinglePage-XIAXT54H.js} +12 -10
  12. package/dist/BlogSinglePage-XIAXT54H.js.map +1 -0
  13. package/dist/{BlogTagPage-TOVPBETA.js → BlogTagPage-IHOA267V.js} +13 -11
  14. package/dist/BlogTagPage-IHOA267V.js.map +1 -0
  15. package/dist/{BrandsPage-VUDGYSQI.js → BrandsPage-ZFEILRT3.js} +12 -10
  16. package/dist/BrandsPage-ZFEILRT3.js.map +1 -0
  17. package/dist/{Breadcrumb-CI3SN5YR.js → Breadcrumb-CVSISL2K.js} +12 -10
  18. package/dist/Breadcrumb-CVSISL2K.js.map +1 -0
  19. package/dist/{CartItem-BHLEURAB.js → CartItem-EOI7P3JA.js} +12 -10
  20. package/dist/CartItem-EOI7P3JA.js.map +1 -0
  21. package/dist/{CartSummary-QERVESYN.js → CartSummary-RZGUBDKJ.js} +12 -10
  22. package/dist/CartSummary-RZGUBDKJ.js.map +1 -0
  23. package/dist/{Copyright-RL6ALKRC.js → Copyright-5BLYSP24.js} +4 -4
  24. package/dist/{Copyright-RL6ALKRC.js.map → Copyright-5BLYSP24.js.map} +1 -1
  25. package/dist/{CustomerLayout-VRZUWGQK.js → CustomerLayout-6F4BJAMU.js} +12 -10
  26. package/dist/CustomerLayout-6F4BJAMU.js.map +1 -0
  27. package/dist/{Footer-ZWBZCMDL.js → Footer-NF2NLDLR.js} +6 -6
  28. package/dist/{Footer-ZWBZCMDL.js.map → Footer-NF2NLDLR.js.map} +1 -1
  29. package/dist/{Header-WNK4O66B.js → Header-6DQJ5X7J.js} +9 -7
  30. package/dist/{Header-WNK4O66B.js.map → Header-6DQJ5X7J.js.map} +1 -1
  31. package/dist/{LoyaltyPage-CGTHWJ76.js → LoyaltyPage-YHWHIWCS.js} +12 -10
  32. package/dist/LoyaltyPage-YHWHIWCS.js.map +1 -0
  33. package/dist/{LoyaltyPointsPage-QSU6K3PW.js → LoyaltyPointsPage-T6K3H5A5.js} +10 -3
  34. package/dist/LoyaltyPointsPage-T6K3H5A5.js.map +1 -0
  35. package/dist/{MainMenu-V3T7F32R.js → MainMenu-Y2TAM7DK.js} +4 -4
  36. package/dist/{MainMenu-V3T7F32R.js.map → MainMenu-Y2TAM7DK.js.map} +1 -1
  37. package/dist/{MasterLayout-XQF644LD.js → MasterLayout-GBEEFFPG.js} +12 -10
  38. package/dist/{MasterLayout-XQF644LD.js.map → MasterLayout-GBEEFFPG.js.map} +1 -1
  39. package/dist/{MobileMenu-U4BVQYKH.js → MobileMenu-RE5XVGZL.js} +12 -10
  40. package/dist/MobileMenu-RE5XVGZL.js.map +1 -0
  41. package/dist/{NotificationsPage-ZRRQY7TF.js → NotificationsPage-QAFWZAS3.js} +12 -10
  42. package/dist/NotificationsPage-QAFWZAS3.js.map +1 -0
  43. package/dist/{OrderSinglePage-O3XYVMG5.js → OrderSinglePage-T4PRXTKG.js} +12 -10
  44. package/dist/OrderSinglePage-T4PRXTKG.js.map +1 -0
  45. package/dist/{OrdersPage-CMXZF5MI.js → OrdersPage-3S6ZS56E.js} +12 -10
  46. package/dist/OrdersPage-3S6ZS56E.js.map +1 -0
  47. package/dist/{PageSingle-IIG2US43.js → PageSingle-W3UTXDOW.js} +4 -4
  48. package/dist/{PageSingle-IIG2US43.js.map → PageSingle-W3UTXDOW.js.map} +1 -1
  49. package/dist/{ProductDetails-65GDABI7.js → ProductDetails-X5A7TG3F.js} +14 -7
  50. package/dist/ProductDetails-X5A7TG3F.js.map +1 -0
  51. package/dist/{ProfilePage-SNJ4WXU4.js → ProfilePage-DUH3AILJ.js} +12 -10
  52. package/dist/ProfilePage-DUH3AILJ.js.map +1 -0
  53. package/dist/{ReviewsPage-UT7UT55J.js → ReviewsPage-2C2P7VS3.js} +4 -4
  54. package/dist/{ReviewsPage-UT7UT55J.js.map → ReviewsPage-2C2P7VS3.js.map} +1 -1
  55. package/dist/{SeoCartWidget-DBJAXJKT.js → SeoCartWidget-VYLGEW2I.js} +12 -10
  56. package/dist/SeoCartWidget-VYLGEW2I.js.map +1 -0
  57. package/dist/{SettingsPage-FLTIOPU5.js → SettingsPage-OZJEW6CW.js} +12 -10
  58. package/dist/SettingsPage-OZJEW6CW.js.map +1 -0
  59. package/dist/{StoreClosedNotification-ZT3QKDKS.js → StoreClosedNotification-QHIZBNXO.js} +13 -11
  60. package/dist/{StoreClosedNotification-ZT3QKDKS.js.map → StoreClosedNotification-QHIZBNXO.js.map} +1 -1
  61. package/dist/{TestimonialsPage-CU7HKU4O.js → TestimonialsPage-K6WJKKME.js} +4 -4
  62. package/dist/{TestimonialsPage-CU7HKU4O.js.map → TestimonialsPage-K6WJKKME.js.map} +1 -1
  63. package/dist/{ThankYouPage-5X6B4X3T.js → ThankYouPage-H6QQEI5R.js} +12 -10
  64. package/dist/ThankYouPage-H6QQEI5R.js.map +1 -0
  65. package/dist/{WalletPage-H2BKIDPA.js → WalletPage-G5IBGN6B.js} +12 -10
  66. package/dist/WalletPage-G5IBGN6B.js.map +1 -0
  67. package/dist/{WishlistPage-RS5UCIQ3.js → WishlistPage-AIOTAB6L.js} +12 -10
  68. package/dist/WishlistPage-AIOTAB6L.js.map +1 -0
  69. package/dist/api/advertisement.d.ts +33 -0
  70. package/dist/api/advertisement.d.ts.map +1 -0
  71. package/dist/api/advertisement.js +8 -0
  72. package/dist/api/advertisement.js.map +1 -0
  73. package/dist/boot/critical-style.d.ts +3 -0
  74. package/dist/boot/critical-style.d.ts.map +1 -0
  75. package/dist/boot/index.d.ts +15 -0
  76. package/dist/boot/index.d.ts.map +1 -0
  77. package/dist/boot.d.ts +1 -0
  78. package/dist/boot.js +69 -0
  79. package/dist/boot.js.map +1 -0
  80. package/dist/chunk-4FJGZQD3.js +6 -0
  81. package/dist/chunk-4FJGZQD3.js.map +1 -0
  82. package/dist/{chunk-SDFDS4Q3.js → chunk-BCJTF3V7.js} +20 -4
  83. package/dist/chunk-BCJTF3V7.js.map +1 -0
  84. package/dist/chunk-DBW7XXNE.js +103 -0
  85. package/dist/chunk-DBW7XXNE.js.map +1 -0
  86. package/dist/{chunk-22SH7OVF.js → chunk-E7WWO3V3.js} +3 -3
  87. package/dist/{chunk-22SH7OVF.js.map → chunk-E7WWO3V3.js.map} +1 -1
  88. package/dist/{chunk-SE5JYWOK.js → chunk-EZHXVHXY.js} +4 -4
  89. package/dist/{chunk-SE5JYWOK.js.map → chunk-EZHXVHXY.js.map} +1 -1
  90. package/dist/{chunk-JO7JWV34.js → chunk-FWYKOQV5.js} +5 -5
  91. package/dist/chunk-FWYKOQV5.js.map +1 -0
  92. package/dist/{chunk-JRNH3FUF.js → chunk-FYHF62KI.js} +4 -4
  93. package/dist/{chunk-JRNH3FUF.js.map → chunk-FYHF62KI.js.map} +1 -1
  94. package/dist/{chunk-UJKFBZFJ.js → chunk-GJ5IMPNY.js} +7 -7
  95. package/dist/{chunk-UJKFBZFJ.js.map → chunk-GJ5IMPNY.js.map} +1 -1
  96. package/dist/chunk-IVBOLREE.js +29 -0
  97. package/dist/chunk-IVBOLREE.js.map +1 -0
  98. package/dist/chunk-LWPKBVIE.js +14 -0
  99. package/dist/{chunk-7WAXN2PP.js.map → chunk-LWPKBVIE.js.map} +1 -1
  100. package/dist/{chunk-SX5MF2YA.js → chunk-N3BCZIGS.js} +4 -4
  101. package/dist/{chunk-SX5MF2YA.js.map → chunk-N3BCZIGS.js.map} +1 -1
  102. package/dist/{chunk-NZVR4OF7.js → chunk-NXQZ4RLM.js} +8 -8
  103. package/dist/chunk-NXQZ4RLM.js.map +1 -0
  104. package/dist/{chunk-NXQJA7D3.js → chunk-PCURRKIH.js} +3 -3
  105. package/dist/{chunk-NXQJA7D3.js.map → chunk-PCURRKIH.js.map} +1 -1
  106. package/dist/{chunk-4CZRUAZB.js → chunk-PU2LHFDQ.js} +10 -11
  107. package/dist/chunk-PU2LHFDQ.js.map +1 -0
  108. package/dist/{chunk-OWX2CPNQ.js → chunk-UQIVX4YA.js} +3 -3
  109. package/dist/{chunk-OWX2CPNQ.js.map → chunk-UQIVX4YA.js.map} +1 -1
  110. package/dist/chunk-X5NK4TZH.js +228 -0
  111. package/dist/chunk-X5NK4TZH.js.map +1 -0
  112. package/dist/{chunk-6SWDSRIL.js → chunk-XKNXYQ2K.js} +6 -6
  113. package/dist/{chunk-6SWDSRIL.js.map → chunk-XKNXYQ2K.js.map} +1 -1
  114. package/dist/{chunk-KHBSGD2D.js → chunk-XO7B3VWS.js} +3 -3
  115. package/dist/{chunk-KHBSGD2D.js.map → chunk-XO7B3VWS.js.map} +1 -1
  116. package/dist/cli/check-manifest.d.ts +10 -0
  117. package/dist/cli/check-manifest.d.ts.map +1 -0
  118. package/dist/cli/check-manifest.js +44 -0
  119. package/dist/cli/check-manifest.js.map +1 -0
  120. package/dist/components/cart/index.js +1 -1
  121. package/dist/components/common/index.js +2 -2
  122. package/dist/components/drawer/index.js +10 -8
  123. package/dist/components/home/index.js +12 -10
  124. package/dist/components/layout/Footer.d.ts.map +1 -1
  125. package/dist/components/layout/MasterLayout.d.ts.map +1 -1
  126. package/dist/components/layout/PageAdvertisements.d.ts +7 -0
  127. package/dist/components/layout/PageAdvertisements.d.ts.map +1 -0
  128. package/dist/components/layout/index.d.ts +1 -0
  129. package/dist/components/layout/index.d.ts.map +1 -1
  130. package/dist/components/layout/index.js +8 -1
  131. package/dist/components/product/index.js +11 -9
  132. package/dist/components/store/index.js +12 -10
  133. package/dist/components/store/index.js.map +1 -1
  134. package/dist/{document-class-67HTP45G.js → document-class-FRL7JHWD.js} +6 -5
  135. package/dist/{document-class-67HTP45G.js.map → document-class-FRL7JHWD.js.map} +1 -1
  136. package/dist/hooks/index.js +10 -8
  137. package/dist/hooks/useGtm.js +10 -8
  138. package/dist/hooks/useMoney.js +10 -8
  139. package/dist/hooks/useStore.js +10 -8
  140. package/dist/index.js +10 -8
  141. package/dist/index.js.map +1 -1
  142. package/dist/{not-found-QTHBFPNT.js → not-found-E6AR4CZ4.js} +12 -10
  143. package/dist/not-found-E6AR4CZ4.js.map +1 -0
  144. package/dist/providers/index.js +10 -8
  145. package/dist/routes/account/orders.js +1 -1
  146. package/dist/routes/account.js +6 -6
  147. package/dist/routes/blog.js +5 -5
  148. package/dist/routes/brands.js +1 -1
  149. package/dist/routes/cart.js +14 -12
  150. package/dist/routes/cart.js.map +1 -1
  151. package/dist/routes/home.js +13 -11
  152. package/dist/routes/loyalty.js +2 -2
  153. package/dist/routes/page.js +1 -1
  154. package/dist/routes/product-listing.js +12 -10
  155. package/dist/routes/product-listing.js.map +1 -1
  156. package/dist/routes/product.js +12 -10
  157. package/dist/routes/product.js.map +1 -1
  158. package/dist/routes/root/beforeLoad.d.ts +2 -2
  159. package/dist/routes/root/beforeLoad.d.ts.map +1 -1
  160. package/dist/routes/root/head.d.ts.map +1 -1
  161. package/dist/routes/testimonials.js +2 -2
  162. package/dist/routes/thank-you.js +1 -1
  163. package/dist/skeleton.js +13 -11
  164. package/dist/tanstack/document-class.d.ts.map +1 -1
  165. package/dist/tanstack/index.d.ts +1 -0
  166. package/dist/tanstack/index.d.ts.map +1 -1
  167. package/dist/tanstack/stylesheet.d.ts +15 -0
  168. package/dist/tanstack/stylesheet.d.ts.map +1 -0
  169. package/dist/tanstack.js +33 -4
  170. package/dist/tanstack.js.map +1 -1
  171. package/dist/utils/index.js +2 -2
  172. package/dist/vite/index.d.ts +1 -1
  173. package/dist/vite/index.d.ts.map +1 -1
  174. package/dist/vite/index.js +14 -25
  175. package/dist/vite/index.js.map +1 -1
  176. package/dist/vite/plugins/twilight-manifest.d.ts +23 -0
  177. package/dist/vite/plugins/twilight-manifest.d.ts.map +1 -1
  178. package/dist/vite/plugins/twilight-schema.plugin.d.ts.map +1 -1
  179. package/package.json +16 -3
  180. package/tailwind/components/Component.tsx.txt +21 -0
  181. package/tailwind/components/ComponentRegistry.ts.txt +66 -0
  182. package/tailwind/components/cart/CartItem.tsx.txt +211 -0
  183. package/tailwind/components/cart/CartSummary.tsx.txt +269 -0
  184. package/tailwind/components/cart/PreOrderNotification.tsx.txt +16 -0
  185. package/tailwind/components/cart/PriceQuoteButton.tsx.txt +21 -0
  186. package/tailwind/components/cart/SeoCartWidget.tsx.txt +99 -0
  187. package/tailwind/components/cart/index.ts.txt +11 -0
  188. package/tailwind/components/cart/lazy.ts.txt +15 -0
  189. package/tailwind/components/collapse/Collapse.tsx.txt +95 -0
  190. package/tailwind/components/collapse/index.ts.txt +41 -0
  191. package/tailwind/components/common/Breadcrumb.tsx.txt +27 -0
  192. package/tailwind/components/common/BreadcrumbJsonLd.tsx.txt +40 -0
  193. package/tailwind/components/common/BreadcrumbTrail.tsx.txt +97 -0
  194. package/tailwind/components/common/CurrencySymbol.tsx.txt +8 -0
  195. package/tailwind/components/common/DeferredData.tsx.txt +122 -0
  196. package/tailwind/components/common/ErrorPage.tsx.txt +69 -0
  197. package/tailwind/components/common/Image.tsx.txt +119 -0
  198. package/tailwind/components/common/Link.tsx.txt +36 -0
  199. package/tailwind/components/common/Link.types.ts.txt +85 -0
  200. package/tailwind/components/common/NoContent.tsx.txt +27 -0
  201. package/tailwind/components/common/RenderWhenVisible.tsx.txt +92 -0
  202. package/tailwind/components/common/index.ts.txt +42 -0
  203. package/tailwind/components/common/lazy.ts.txt +28 -0
  204. package/tailwind/components/defineComponent.ts.txt +32 -0
  205. package/tailwind/components/drawer/Drawer.tsx.txt +219 -0
  206. package/tailwind/components/drawer/index.ts.txt +38 -0
  207. package/tailwind/components/dropdown/Dropdown.tsx.txt +166 -0
  208. package/tailwind/components/dropdown/index.ts.txt +40 -0
  209. package/tailwind/components/home/BundleComponent.tsx.txt +35 -0
  210. package/tailwind/components/home/ComponentErrorBoundary.tsx.txt +98 -0
  211. package/tailwind/components/home/FeaturedProductsStyle1.tsx.txt +125 -0
  212. package/tailwind/components/home/FeaturedProductsStyle2.tsx.txt +127 -0
  213. package/tailwind/components/home/FeaturedProductsStyle3.tsx.txt +71 -0
  214. package/tailwind/components/home/FixedBanner.tsx.txt +68 -0
  215. package/tailwind/components/home/FixedProducts.tsx.txt +72 -0
  216. package/tailwind/components/home/HomePageRenderer.tsx.txt +284 -0
  217. package/tailwind/components/home/PageBlocks.tsx.txt +24 -0
  218. package/tailwind/components/home/ParallaxBackground.tsx.txt +50 -0
  219. package/tailwind/components/home/PhotosSlider.tsx.txt +61 -0
  220. package/tailwind/components/home/ProductsSlider.tsx.txt +87 -0
  221. package/tailwind/components/home/ReorderProducts.tsx.txt +38 -0
  222. package/tailwind/components/home/SquarePhotos.tsx.txt +68 -0
  223. package/tailwind/components/home/StoreFeatures.tsx.txt +34 -0
  224. package/tailwind/components/home/Testimonials.tsx.txt +36 -0
  225. package/tailwind/components/home/Youtube.tsx.txt +48 -0
  226. package/tailwind/components/home/index.ts.txt +50 -0
  227. package/tailwind/components/home/lazy.ts.txt +17 -0
  228. package/tailwind/components/home/register.ts.txt +44 -0
  229. package/tailwind/components/home/reorder-hooks.ts.txt +52 -0
  230. package/tailwind/components/home/types.ts.txt +4 -0
  231. package/tailwind/components/layout/Copyright.tsx.txt +23 -0
  232. package/tailwind/components/layout/CustomerLayout.tsx.txt +91 -0
  233. package/tailwind/components/layout/Footer.tsx.txt +341 -0
  234. package/tailwind/components/layout/Header.tsx.txt +154 -0
  235. package/tailwind/components/layout/MasterLayout.tsx.txt +62 -0
  236. package/tailwind/components/layout/PageAdvertisements.tsx.txt +131 -0
  237. package/tailwind/components/layout/WidgetHead.tsx.txt +53 -0
  238. package/tailwind/components/layout/index.ts.txt +27 -0
  239. package/tailwind/components/layout/lazy.ts.txt +25 -0
  240. package/tailwind/components/layout/types.ts.txt +11 -0
  241. package/tailwind/components/modal/ImageModal.tsx.txt +70 -0
  242. package/tailwind/components/modal/Modal.tsx.txt +245 -0
  243. package/tailwind/components/modal/index.ts.txt +39 -0
  244. package/tailwind/components/modal/lazy.ts.txt +10 -0
  245. package/tailwind/components/navigation/MainMenu.tsx.txt +94 -0
  246. package/tailwind/components/navigation/MobileMenu.tsx.txt +160 -0
  247. package/tailwind/components/navigation/index.ts.txt +4 -0
  248. package/tailwind/components/navigation/lazy.ts.txt +11 -0
  249. package/tailwind/components/product/AddToCartForm.tsx.txt +365 -0
  250. package/tailwind/components/product/ProductCard.tsx.txt +305 -0
  251. package/tailwind/components/product/ProductDetails.tsx.txt +267 -0
  252. package/tailwind/components/product/ProductGallery.tsx.txt +234 -0
  253. package/tailwind/components/product/index.ts.txt +15 -0
  254. package/tailwind/components/product/lazy.ts.txt +9 -0
  255. package/tailwind/components/snippets/AppsSnippets.tsx.txt +225 -0
  256. package/tailwind/components/snippets/SallaSdkScripts.tsx.txt +14 -0
  257. package/tailwind/components/snippets/index.ts.txt +4 -0
  258. package/tailwind/components/store/StoreClosedNotification.tsx.txt +73 -0
  259. package/tailwind/components/store/index.ts.txt +8 -0
  260. package/tailwind/components/store/lazy.ts.txt +8 -0
  261. package/tailwind/components/toast/Toaster.tsx.txt +177 -0
  262. package/tailwind/components/toast/index.ts.txt +10 -0
  263. package/tailwind/components/toast/types.ts.txt +38 -0
  264. package/tailwind/components/toast/useToast.ts.txt +61 -0
  265. package/tailwind/components/tracking/GoogleTag.tsx.txt +77 -0
  266. package/tailwind/components/tracking/SiftSnippet.tsx.txt +49 -0
  267. package/tailwind/components/tracking/TwilightBundles.tsx.txt +67 -0
  268. package/tailwind/components/tracking/index.ts.txt +11 -0
  269. package/tailwind/components/tracking/lazy.ts.txt +11 -0
  270. package/tailwind/components/useComponent.ts.txt +12 -0
  271. package/tailwind/routes/account/index.tsx.txt +16 -0
  272. package/tailwind/routes/account/notifications/NotificationsPage.tsx.txt +54 -0
  273. package/tailwind/routes/account/notifications/index.tsx.txt +39 -0
  274. package/tailwind/routes/account/notifications/loader.ts.txt +22 -0
  275. package/tailwind/routes/account/notifications/types.ts.txt +18 -0
  276. package/tailwind/routes/account/orders/OrderSinglePage.tsx.txt +181 -0
  277. package/tailwind/routes/account/orders/OrdersPage.tsx.txt +28 -0
  278. package/tailwind/routes/account/orders/index.tsx.txt +86 -0
  279. package/tailwind/routes/account/orders/loader.ts.txt +66 -0
  280. package/tailwind/routes/account/orders/single/OrderActions.tsx.txt +197 -0
  281. package/tailwind/routes/account/orders/single/OrderHeader.tsx.txt +143 -0
  282. package/tailwind/routes/account/orders/single/OrderItemRow.tsx.txt +209 -0
  283. package/tailwind/routes/account/orders/single/OrderSections.tsx.txt +215 -0
  284. package/tailwind/routes/account/orders/single/types.ts.txt +38 -0
  285. package/tailwind/routes/account/orders/types.ts.txt +29 -0
  286. package/tailwind/routes/account/profile/ProfilePage.tsx.txt +30 -0
  287. package/tailwind/routes/account/profile/index.tsx.txt +39 -0
  288. package/tailwind/routes/account/profile/loader.ts.txt +16 -0
  289. package/tailwind/routes/account/profile/types.ts.txt +5 -0
  290. package/tailwind/routes/account/settings/SettingsPage.tsx.txt +13 -0
  291. package/tailwind/routes/account/settings/index.tsx.txt +39 -0
  292. package/tailwind/routes/account/settings/loader.ts.txt +13 -0
  293. package/tailwind/routes/account/settings/types.ts.txt +5 -0
  294. package/tailwind/routes/account/types.ts.txt +320 -0
  295. package/tailwind/routes/account/wallet/WalletPage.tsx.txt +195 -0
  296. package/tailwind/routes/account/wallet/index.tsx.txt +38 -0
  297. package/tailwind/routes/account/wallet/loader.ts.txt +27 -0
  298. package/tailwind/routes/account/wallet/types.ts.txt +11 -0
  299. package/tailwind/routes/account/wishlist/WishlistPage.tsx.txt +314 -0
  300. package/tailwind/routes/account/wishlist/WishlistShare.tsx.txt +248 -0
  301. package/tailwind/routes/account/wishlist/index.tsx.txt +40 -0
  302. package/tailwind/routes/account/wishlist/loader.ts.txt +26 -0
  303. package/tailwind/routes/account/wishlist/types.ts.txt +9 -0
  304. package/tailwind/routes/blog/BlogAuthorPage.tsx.txt +82 -0
  305. package/tailwind/routes/blog/BlogCategoryPage.tsx.txt +84 -0
  306. package/tailwind/routes/blog/BlogPage.tsx.txt +191 -0
  307. package/tailwind/routes/blog/BlogSinglePage.tsx.txt +168 -0
  308. package/tailwind/routes/blog/BlogTagPage.tsx.txt +84 -0
  309. package/tailwind/routes/blog/BlogTags.tsx.txt +54 -0
  310. package/tailwind/routes/blog/components.tsx.txt +87 -0
  311. package/tailwind/routes/blog/index.tsx.txt +238 -0
  312. package/tailwind/routes/blog/loader.ts.txt +173 -0
  313. package/tailwind/routes/blog/types.ts.txt +106 -0
  314. package/tailwind/routes/brands/BrandsPage.tsx.txt +179 -0
  315. package/tailwind/routes/brands/index.tsx.txt +47 -0
  316. package/tailwind/routes/brands/loader.ts.txt +14 -0
  317. package/tailwind/routes/brands/types.ts.txt +53 -0
  318. package/tailwind/routes/cart/CartPage.tsx.txt +112 -0
  319. package/tailwind/routes/cart/index.tsx.txt +39 -0
  320. package/tailwind/routes/cart/loader.ts.txt +15 -0
  321. package/tailwind/routes/cart/types.ts.txt +6 -0
  322. package/tailwind/routes/home/HomePage.tsx.txt +53 -0
  323. package/tailwind/routes/home/index.tsx.txt +56 -0
  324. package/tailwind/routes/home/loader.ts.txt +24 -0
  325. package/tailwind/routes/home/types.ts.txt +11 -0
  326. package/tailwind/routes/index.ts.txt +94 -0
  327. package/tailwind/routes/lazy.ts.txt +105 -0
  328. package/tailwind/routes/loyalty/LoyaltyPage.tsx.txt +17 -0
  329. package/tailwind/routes/loyalty/LoyaltyPointsPage.tsx.txt +11 -0
  330. package/tailwind/routes/loyalty/index.tsx.txt +81 -0
  331. package/tailwind/routes/loyalty/loader.ts.txt +53 -0
  332. package/tailwind/routes/loyalty/types.ts.txt +109 -0
  333. package/tailwind/routes/not-found.tsx.txt +50 -0
  334. package/tailwind/routes/page/PageSingle.tsx.txt +34 -0
  335. package/tailwind/routes/page/index.tsx.txt +60 -0
  336. package/tailwind/routes/page/loader.ts.txt +19 -0
  337. package/tailwind/routes/page/types.ts.txt +34 -0
  338. package/tailwind/routes/product/ProductPage.tsx.txt +77 -0
  339. package/tailwind/routes/product/index.tsx.txt +157 -0
  340. package/tailwind/routes/product/loader.ts.txt +35 -0
  341. package/tailwind/routes/product/types.ts.txt +6 -0
  342. package/tailwind/routes/product-listing/ProductListPage.tsx.txt +321 -0
  343. package/tailwind/routes/product-listing/index.tsx.txt +95 -0
  344. package/tailwind/routes/product-listing/loader.ts.txt +323 -0
  345. package/tailwind/routes/product-listing/types.ts.txt +96 -0
  346. package/tailwind/routes/product-listing/useAppliedFilters.ts.txt +130 -0
  347. package/tailwind/routes/product-short-link.ts.txt +104 -0
  348. package/tailwind/routes/root/beforeLoad.ts.txt +354 -0
  349. package/tailwind/routes/root/head.ts.txt +424 -0
  350. package/tailwind/routes/root/index.ts.txt +2 -0
  351. package/tailwind/routes/seo-redirects.ts.txt +82 -0
  352. package/tailwind/routes/testimonials/ReviewsPage.tsx.txt +12 -0
  353. package/tailwind/routes/testimonials/TestimonialsPage.tsx.txt +80 -0
  354. package/tailwind/routes/testimonials/index.tsx.txt +80 -0
  355. package/tailwind/routes/testimonials/loader.ts.txt +29 -0
  356. package/tailwind/routes/testimonials/types.ts.txt +9 -0
  357. package/tailwind/routes/thank-you/ThankYouPage.tsx.txt +241 -0
  358. package/tailwind/routes/thank-you/index.tsx.txt +49 -0
  359. package/tailwind/routes/thank-you/loader.ts.txt +40 -0
  360. package/tailwind/routes/thank-you/types.ts.txt +16 -0
  361. package/tailwind/routes/types.ts.txt +41 -0
  362. package/dist/BlogAuthorPage-GTF4UGSS.js.map +0 -1
  363. package/dist/BlogCategoryPage-TN7Y4NBP.js.map +0 -1
  364. package/dist/BlogPage-TDB3M3PI.js.map +0 -1
  365. package/dist/BlogSinglePage-4A52Q5QW.js.map +0 -1
  366. package/dist/BlogTagPage-TOVPBETA.js.map +0 -1
  367. package/dist/BrandsPage-VUDGYSQI.js.map +0 -1
  368. package/dist/Breadcrumb-CI3SN5YR.js.map +0 -1
  369. package/dist/CartItem-BHLEURAB.js.map +0 -1
  370. package/dist/CartSummary-QERVESYN.js.map +0 -1
  371. package/dist/CustomerLayout-VRZUWGQK.js.map +0 -1
  372. package/dist/LoyaltyPage-CGTHWJ76.js.map +0 -1
  373. package/dist/LoyaltyPointsPage-QSU6K3PW.js.map +0 -1
  374. package/dist/MobileMenu-U4BVQYKH.js.map +0 -1
  375. package/dist/NotificationsPage-ZRRQY7TF.js.map +0 -1
  376. package/dist/OrderSinglePage-O3XYVMG5.js.map +0 -1
  377. package/dist/OrdersPage-CMXZF5MI.js.map +0 -1
  378. package/dist/ProductDetails-65GDABI7.js.map +0 -1
  379. package/dist/ProfilePage-SNJ4WXU4.js.map +0 -1
  380. package/dist/SeoCartWidget-DBJAXJKT.js.map +0 -1
  381. package/dist/SettingsPage-FLTIOPU5.js.map +0 -1
  382. package/dist/ThankYouPage-5X6B4X3T.js.map +0 -1
  383. package/dist/WalletPage-H2BKIDPA.js.map +0 -1
  384. package/dist/WishlistPage-RS5UCIQ3.js.map +0 -1
  385. package/dist/chunk-4CZRUAZB.js.map +0 -1
  386. package/dist/chunk-7WAXN2PP.js +0 -14
  387. package/dist/chunk-JO7JWV34.js.map +0 -1
  388. package/dist/chunk-NZVR4OF7.js.map +0 -1
  389. package/dist/chunk-SDFDS4Q3.js.map +0 -1
  390. package/dist/not-found-QTHBFPNT.js.map +0 -1
@@ -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';
@@ -0,0 +1,28 @@
1
+ import { lazy } from 'react';
2
+
3
+ /**
4
+ * Lazy-loaded common components
5
+ * Use these for code-splitting non-critical components
6
+ *
7
+ * NOTE: Skeleton components are sourced from @salla.sa/twilight-components-react
8
+ * because they must render instantly during route transitions.
9
+ */
10
+ export const LazyBreadcrumb = lazy(() =>
11
+ import('./Breadcrumb').then((m) => ({ default: m.Breadcrumb }))
12
+ );
13
+
14
+ export const LazyNoContent = lazy(() =>
15
+ import('./NoContent').then((m) => ({ default: m.NoContent }))
16
+ );
17
+
18
+ export const LazyRenderWhenVisible = lazy(() =>
19
+ import('./RenderWhenVisible').then((m) => ({ default: m.RenderWhenVisible }))
20
+ );
21
+
22
+ export const LazyCurrencySymbol = lazy(() =>
23
+ import('./CurrencySymbol').then((m) => ({ default: m.CurrencySymbol }))
24
+ );
25
+
26
+ export const LazyErrorPage = lazy(() =>
27
+ import('./ErrorPage').then((m) => ({ default: m.ErrorPage }))
28
+ );
@@ -0,0 +1,32 @@
1
+ import { registry } from './ComponentRegistry';
2
+ import type { ComponentType } from '../types';
3
+
4
+ interface DefineComponentConfig<P = Record<string, unknown>> {
5
+ name: string;
6
+ component: ComponentType<P>;
7
+ override?: boolean;
8
+ }
9
+
10
+ export function defineComponent<P = Record<string, unknown>>(
11
+ config: DefineComponentConfig<P>
12
+ ): ComponentType<P> {
13
+ if (config.override) {
14
+ registry.override(config.name, config.component);
15
+ } else {
16
+ registry.register(config.name, config.component);
17
+ }
18
+
19
+ return config.component;
20
+ }
21
+
22
+ export function registerComponents(components: Record<string, ComponentType>): void {
23
+ for (const [name, component] of Object.entries(components)) {
24
+ registry.register(name, component);
25
+ }
26
+ }
27
+
28
+ export function overrideComponents(components: Record<string, ComponentType>): void {
29
+ for (const [name, component] of Object.entries(components)) {
30
+ registry.override(name, component);
31
+ }
32
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Drawer Component
3
+ *
4
+ * A slide-in panel from the side of the screen, commonly used for mobile menus.
5
+ */
6
+
7
+ import React, { useEffect, useCallback, useRef, type ReactNode, type HTMLAttributes } from 'react';
8
+ import { createPortal } from 'react-dom';
9
+ import { useTwilight } from '../../providers/TwilightProvider';
10
+
11
+ export interface DrawerProps {
12
+ isOpen: boolean;
13
+ onClose: () => void;
14
+ children: ReactNode;
15
+ /**
16
+ * Width of the drawer
17
+ * @default 'md'
18
+ */
19
+ size?: 'sm' | 'md' | 'lg' | 'full';
20
+ /**
21
+ * Close drawer when clicking backdrop
22
+ * @default true
23
+ */
24
+ closeOnBackdropClick?: boolean;
25
+ /**
26
+ * Close drawer on Escape key
27
+ * @default true
28
+ */
29
+ closeOnEscape?: boolean;
30
+ /**
31
+ * Prevent body scroll when drawer is open
32
+ * @default true
33
+ */
34
+ preventBodyScroll?: boolean;
35
+ /**
36
+ * Additional className for the drawer content
37
+ */
38
+ className?: string;
39
+ }
40
+
41
+ const sizeClasses = {
42
+ sm: 'w-64',
43
+ md: 'w-80',
44
+ lg: 'w-96',
45
+ full: 'w-full',
46
+ };
47
+
48
+ export function Drawer({
49
+ isOpen,
50
+ onClose,
51
+ children,
52
+ size = 'md',
53
+ closeOnBackdropClick = true,
54
+ closeOnEscape = true,
55
+ preventBodyScroll = true,
56
+ className,
57
+ }: DrawerProps) {
58
+ const drawerRef = useRef<HTMLDivElement>(null);
59
+ const previousActiveElement = useRef<HTMLElement | null>(null);
60
+ // Use TwilightProvider to get theme direction
61
+ // Position is automatically determined: left for LTR, right for RTL
62
+ const { theme } = useTwilight();
63
+ const isRtl = theme?.is_rtl ?? false;
64
+ const position = isRtl ? 'right' : 'left';
65
+
66
+ // Handle Escape key
67
+ const handleKeyDown = useCallback(
68
+ (event: KeyboardEvent) => {
69
+ if (event.key === 'Escape' && closeOnEscape) {
70
+ onClose();
71
+ }
72
+ },
73
+ [closeOnEscape, onClose]
74
+ );
75
+
76
+ // Handle backdrop click
77
+ const handleBackdropClick = useCallback(
78
+ (_event: React.MouseEvent) => {
79
+ if (closeOnBackdropClick) {
80
+ onClose();
81
+ }
82
+ },
83
+ [closeOnBackdropClick, onClose]
84
+ );
85
+
86
+ // Focus management and body scroll
87
+ useEffect(() => {
88
+ if (isOpen) {
89
+ // Store the previously focused element
90
+ previousActiveElement.current = document.activeElement as HTMLElement;
91
+
92
+ // Focus the drawer
93
+ drawerRef.current?.focus();
94
+
95
+ // Prevent body scroll
96
+ if (preventBodyScroll) {
97
+ document.body.classList.add('menu-opened');
98
+ }
99
+
100
+ // Add keyboard listener
101
+ document.addEventListener('keydown', handleKeyDown);
102
+ }
103
+
104
+ return () => {
105
+ document.removeEventListener('keydown', handleKeyDown);
106
+
107
+ // Restore body scroll
108
+ if (preventBodyScroll) {
109
+ document.body.classList.remove('menu-opened');
110
+ }
111
+
112
+ // Restore focus
113
+ previousActiveElement.current?.focus();
114
+ };
115
+ }, [isOpen, handleKeyDown, preventBodyScroll]);
116
+
117
+ if (!isOpen || typeof document === 'undefined') {
118
+ return null;
119
+ }
120
+
121
+ // Translate classes: left drawer slides from -100%, right drawer slides from +100%
122
+ const translateClass = isOpen
123
+ ? 'translate-x-0'
124
+ : position === 'left'
125
+ ? '-translate-x-full'
126
+ : 'translate-x-full';
127
+
128
+ return createPortal(
129
+ <div className="fixed inset-0 z-[200]" aria-modal="true" role="dialog">
130
+ {/* Backdrop */}
131
+ <div
132
+ className={`absolute inset-0 bg-black/50 transition-opacity duration-300 ${isOpen ? 'opacity-100' : 'opacity-0'}`}
133
+ onClick={handleBackdropClick}
134
+ onKeyDown={(e) => {
135
+ if ((e.key === 'Enter' || e.key === ' ') && closeOnBackdropClick) {
136
+ e.preventDefault();
137
+ onClose();
138
+ }
139
+ }}
140
+ role="presentation"
141
+ />
142
+
143
+ {/* Drawer Content */}
144
+ <div
145
+ ref={drawerRef}
146
+ tabIndex={-1}
147
+ className={`fixed top-0 bottom-0 flex flex-col bg-white shadow-xl ${sizeClasses[size]} ${position === 'left' ? 'left-0' : 'right-0'} transform transition-transform duration-300 ease-out ${translateClass} ${className || ''}`}
148
+ >
149
+ {children}
150
+ </div>
151
+ </div>,
152
+ document.body
153
+ );
154
+ }
155
+
156
+ // Drawer Sub-components
157
+ export interface DrawerHeaderProps extends HTMLAttributes<HTMLDivElement> {
158
+ children: ReactNode;
159
+ showCloseButton?: boolean;
160
+ onClose?: () => void;
161
+ }
162
+
163
+ Drawer.Header = function DrawerHeader({
164
+ children,
165
+ showCloseButton = true,
166
+ onClose,
167
+ className,
168
+ ...props
169
+ }: DrawerHeaderProps) {
170
+ return (
171
+ <div
172
+ className={`flex items-center justify-between border-b border-gray-200 px-4 py-3 ${className || ''}`}
173
+ {...props}
174
+ >
175
+ <h2 className="text-lg font-semibold text-gray-900">{children}</h2>
176
+ {showCloseButton && onClose && (
177
+ <button
178
+ type="button"
179
+ onClick={onClose}
180
+ className="rounded-md p-2 text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-500"
181
+ >
182
+ <span className="sr-only">Close</span>
183
+ <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
184
+ <path
185
+ strokeLinecap="round"
186
+ strokeLinejoin="round"
187
+ strokeWidth={2}
188
+ d="M6 18L18 6M6 6l12 12"
189
+ />
190
+ </svg>
191
+ </button>
192
+ )}
193
+ </div>
194
+ );
195
+ };
196
+
197
+ export interface DrawerBodyProps extends HTMLAttributes<HTMLDivElement> {
198
+ children: ReactNode;
199
+ }
200
+
201
+ Drawer.Body = function DrawerBody({ children, className, ...props }: DrawerBodyProps) {
202
+ return (
203
+ <div className={`flex-1 overflow-y-auto px-4 py-3 ${className || ''}`} {...props}>
204
+ {children}
205
+ </div>
206
+ );
207
+ };
208
+
209
+ export interface DrawerFooterProps extends HTMLAttributes<HTMLDivElement> {
210
+ children: ReactNode;
211
+ }
212
+
213
+ Drawer.Footer = function DrawerFooter({ children, className, ...props }: DrawerFooterProps) {
214
+ return (
215
+ <div className={`border-t border-gray-200 px-4 py-3 ${className || ''}`} {...props}>
216
+ {children}
217
+ </div>
218
+ );
219
+ };