@akinon/projectzero 2.0.0-beta.19 → 2.0.0-beta.20

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 (223) hide show
  1. package/CHANGELOG.md +9 -7
  2. package/app-template/CHANGELOG.md +251 -204
  3. package/app-template/akinon.json +1 -1
  4. package/app-template/package.json +28 -28
  5. package/app-template/public/amex.svg +12 -0
  6. package/app-template/public/apple-pay.svg +16 -0
  7. package/app-template/public/assets/images/product-placeholder-1.jpg +0 -0
  8. package/app-template/public/assets/images/product-placeholder-2.jpg +0 -0
  9. package/app-template/public/assets/images/product-placeholder-3.jpg +0 -0
  10. package/app-template/public/assets/images/product-placeholder-4.jpg +0 -0
  11. package/app-template/public/google-pay.svg +16 -0
  12. package/app-template/public/locales/en/account.json +6 -3
  13. package/app-template/public/locales/en/auth.json +6 -7
  14. package/app-template/public/locales/en/basket.json +6 -6
  15. package/app-template/public/locales/en/blog.json +7 -0
  16. package/app-template/public/locales/en/category.json +3 -1
  17. package/app-template/public/locales/en/checkout.json +5 -4
  18. package/app-template/public/locales/en/common.json +11 -2
  19. package/app-template/public/locales/en/forgot_password.json +6 -7
  20. package/app-template/public/locales/en/product.json +4 -3
  21. package/app-template/public/locales/tr/account.json +6 -3
  22. package/app-template/public/locales/tr/auth.json +16 -17
  23. package/app-template/public/locales/tr/basket.json +4 -4
  24. package/app-template/public/locales/tr/blog.json +7 -0
  25. package/app-template/public/locales/tr/category.json +3 -1
  26. package/app-template/public/locales/tr/checkout.json +39 -38
  27. package/app-template/public/locales/tr/common.json +10 -1
  28. package/app-template/public/locales/tr/forgot_password.json +12 -13
  29. package/app-template/public/locales/tr/product.json +1 -0
  30. package/app-template/public/logo.svg +3 -27
  31. package/app-template/public/mastercard.svg +14 -0
  32. package/app-template/public/promotion-banner.jpg +0 -0
  33. package/app-template/public/shop-pay.svg +12 -0
  34. package/app-template/public/visa.svg +12 -0
  35. package/app-template/src/app/[commerce]/[locale]/[currency]/blog/[slug]/page.tsx +118 -0
  36. package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +15 -0
  37. package/app-template/src/app/api/theme-settings/route.ts +12 -0
  38. package/app-template/src/assets/fonts/pz-icon.css +211 -49
  39. package/app-template/src/assets/fonts/pz-icon.eot +0 -0
  40. package/app-template/src/assets/fonts/pz-icon.html +486 -0
  41. package/app-template/src/assets/fonts/pz-icon.scss +373 -49
  42. package/app-template/src/assets/fonts/pz-icon.svg +215 -53
  43. package/app-template/src/assets/fonts/pz-icon.ttf +0 -0
  44. package/app-template/src/assets/fonts/pz-icon.woff +0 -0
  45. package/app-template/src/assets/fonts/pz-icon.woff2 +0 -0
  46. package/app-template/src/assets/globals.scss +4 -0
  47. package/app-template/src/assets/icons/arrow-right.svg +3 -0
  48. package/app-template/src/assets/icons/cart.svg +4 -12
  49. package/app-template/src/assets/icons/check.svg +2 -18
  50. package/app-template/src/assets/icons/chevron-down.svg +2 -7
  51. package/app-template/src/assets/icons/delete.svg +3 -0
  52. package/app-template/src/assets/icons/facebook.svg +2 -8
  53. package/app-template/src/assets/icons/fav-off.svg +5 -0
  54. package/app-template/src/assets/icons/fav-on.svg +5 -0
  55. package/app-template/src/assets/icons/filter-and-sort.svg +3 -0
  56. package/app-template/src/assets/icons/heart.svg +3 -0
  57. package/app-template/src/assets/icons/instagram.svg +2 -13
  58. package/app-template/src/assets/icons/materials.svg +3 -0
  59. package/app-template/src/assets/icons/person.svg +4 -0
  60. package/app-template/src/assets/icons/pinterest.svg +5 -11
  61. package/app-template/src/assets/icons/ruler.svg +3 -0
  62. package/app-template/src/assets/icons/search.svg +8 -11
  63. package/app-template/src/assets/icons/share.svg +2 -9
  64. package/app-template/src/assets/icons/snapchat.svg +3 -0
  65. package/app-template/src/assets/icons/tiktok.svg +3 -0
  66. package/app-template/src/assets/icons/tumblr.svg +6 -0
  67. package/app-template/src/assets/icons/twitter.svg +2 -10
  68. package/app-template/src/assets/icons/vimeo.svg +3 -0
  69. package/app-template/src/assets/icons/youtube.svg +3 -0
  70. package/app-template/src/assets/icons/zoom.svg +8 -0
  71. package/app-template/src/components/accordion.tsx +33 -11
  72. package/app-template/src/components/action-tooltip.tsx +160 -0
  73. package/app-template/src/components/currency-select.tsx +149 -4
  74. package/app-template/src/components/icon.tsx +5 -6
  75. package/app-template/src/components/index.ts +4 -1
  76. package/app-template/src/components/language-select.tsx +88 -2
  77. package/app-template/src/components/pagination.tsx +132 -20
  78. package/app-template/src/components/quantity-input.tsx +63 -0
  79. package/app-template/src/components/quantity-selector.tsx +203 -0
  80. package/app-template/src/components/route-handler.tsx +50 -0
  81. package/app-template/src/components/select.tsx +89 -69
  82. package/app-template/src/components/types/index.ts +26 -0
  83. package/app-template/src/components/widget-content.tsx +323 -0
  84. package/app-template/src/data/server/theme.ts +70 -0
  85. package/app-template/src/hooks/use-fav-button.tsx +5 -2
  86. package/app-template/src/hooks/use-product-cart.ts +11 -8
  87. package/app-template/src/hooks/use-theme-settings.ts +42 -0
  88. package/app-template/src/lib/fonts.ts +149 -0
  89. package/app-template/src/settings.js +2 -2
  90. package/app-template/src/types/hookform-resolvers-yup.d.ts +28 -0
  91. package/app-template/src/types/widget.ts +169 -0
  92. package/app-template/src/utils/formatDate.ts +48 -0
  93. package/app-template/src/utils/styles.ts +71 -0
  94. package/app-template/src/views/account/contact-form.tsx +147 -130
  95. package/app-template/src/views/basket/basket-item.tsx +691 -107
  96. package/app-template/src/views/basket/basket-summary-context.tsx +560 -0
  97. package/app-template/src/views/basket/designer-context.tsx +617 -0
  98. package/app-template/src/views/basket/index.ts +2 -0
  99. package/app-template/src/views/basket/summary.tsx +496 -75
  100. package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +190 -0
  101. package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +286 -0
  102. package/app-template/src/views/breadcrumb/constants.ts +15 -0
  103. package/app-template/src/views/breadcrumb/index.tsx +127 -0
  104. package/app-template/src/views/breadcrumb.tsx +13 -38
  105. package/app-template/src/views/category/category-banner.tsx +4 -23
  106. package/app-template/src/views/category/category-header.tsx +289 -66
  107. package/app-template/src/views/category/category-info.tsx +173 -24
  108. package/app-template/src/views/category/filters/filter-item.tsx +138 -42
  109. package/app-template/src/views/category/filters/index.tsx +208 -48
  110. package/app-template/src/views/category/layout.tsx +7 -4
  111. package/app-template/src/views/category/native-widget-context.tsx +257 -0
  112. package/app-template/src/views/category/product-list-registrar.tsx +665 -0
  113. package/app-template/src/views/checkout/auth.tsx +64 -40
  114. package/app-template/src/views/checkout/checkout-address-registrar.tsx +254 -0
  115. package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +183 -0
  116. package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +259 -0
  117. package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +253 -0
  118. package/app-template/src/views/checkout/checkout-summary-registrar.tsx +183 -0
  119. package/app-template/src/views/checkout/constants.ts +5 -0
  120. package/app-template/src/views/checkout/index.tsx +5 -0
  121. package/app-template/src/views/checkout/layout/header.tsx +9 -5
  122. package/app-template/src/views/checkout/steps/payment/index.tsx +5 -2
  123. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +72 -1
  124. package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +15 -0
  125. package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +18 -0
  126. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +171 -40
  127. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +74 -12
  128. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +128 -45
  129. package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +232 -27
  130. package/app-template/src/views/checkout/summary.tsx +303 -29
  131. package/app-template/src/views/footer/footer-app-banner-context.tsx +326 -0
  132. package/app-template/src/views/footer/footer-bottom-context.tsx +215 -0
  133. package/app-template/src/views/footer/footer-bottom-wrapper.tsx +74 -0
  134. package/app-template/src/views/footer/footer-layout-constants.ts +35 -0
  135. package/app-template/src/views/footer/footer-layout-registrar.tsx +342 -0
  136. package/app-template/src/views/footer/footer-layout-switcher.tsx +110 -0
  137. package/app-template/src/views/footer/footer-menu-context.tsx +211 -0
  138. package/app-template/src/views/footer/footer-native-widgets.tsx +60 -0
  139. package/app-template/src/views/footer/footer-social-context.tsx +254 -0
  140. package/app-template/src/views/footer/footer-subscription-context.tsx +210 -0
  141. package/app-template/src/views/footer/footer-utils.ts +43 -0
  142. package/app-template/src/views/footer/footer-value-props-context.tsx +326 -0
  143. package/app-template/src/views/footer/logo-settings.ts +183 -0
  144. package/app-template/src/views/footer/native-widget-config.ts +262 -0
  145. package/app-template/src/views/footer/subscription-settings.ts +122 -0
  146. package/app-template/src/views/footer/use-footer-logo.ts +162 -0
  147. package/app-template/src/views/footer.tsx +415 -13
  148. package/app-template/src/views/guest-login/index.tsx +62 -58
  149. package/app-template/src/views/header/action-menu.tsx +277 -45
  150. package/app-template/src/views/header/band.tsx +6 -21
  151. package/app-template/src/views/header/designer-context.tsx +261 -0
  152. package/app-template/src/views/header/header-announcement-registrar.tsx +267 -0
  153. package/app-template/src/views/header/header-client-wrapper.tsx +496 -0
  154. package/app-template/src/views/header/header-content.tsx +1026 -0
  155. package/app-template/src/views/header/header-currency-registrar.tsx +348 -0
  156. package/app-template/src/views/header/header-icons-context.tsx +262 -0
  157. package/app-template/src/views/header/header-language-registrar.tsx +348 -0
  158. package/app-template/src/views/header/header-layout-context.tsx +143 -0
  159. package/app-template/src/views/header/header-layout-registrar.tsx +658 -0
  160. package/app-template/src/views/header/header-logo-context.tsx +228 -0
  161. package/app-template/src/views/header/header-logo.tsx +118 -0
  162. package/app-template/src/views/header/header-mini-basket-context.tsx +524 -0
  163. package/app-template/src/views/header/header-search-registrar.tsx +511 -0
  164. package/app-template/src/views/header/header-text-slider-registrar.tsx +382 -0
  165. package/app-template/src/views/header/index.tsx +109 -47
  166. package/app-template/src/views/header/inline-search.tsx +262 -0
  167. package/app-template/src/views/header/mini-basket.tsx +819 -44
  168. package/app-template/src/views/header/mobile-hamburger-button.tsx +5 -8
  169. package/app-template/src/views/header/mobile-menu.tsx +12 -0
  170. package/app-template/src/views/header/navbar-menu-context.tsx +219 -0
  171. package/app-template/src/views/header/navbar.tsx +178 -111
  172. package/app-template/src/views/header/search/index.tsx +71 -32
  173. package/app-template/src/views/header/search/results.tsx +127 -65
  174. package/app-template/src/views/header/search/search-input.tsx +61 -0
  175. package/app-template/src/views/header/server-settings-parser.ts +1105 -0
  176. package/app-template/src/views/header/use-header-icons.ts +241 -0
  177. package/app-template/src/views/header/use-header-logo.ts +213 -0
  178. package/app-template/src/views/header/use-navbar-menu.ts +179 -0
  179. package/app-template/src/views/login/index.tsx +54 -46
  180. package/app-template/src/views/product/accordion-section.tsx +61 -0
  181. package/app-template/src/views/product/accordion-wrapper.tsx +135 -43
  182. package/app-template/src/views/product/custom-button-group.tsx +69 -0
  183. package/app-template/src/views/product/favorites-button-section.tsx +69 -0
  184. package/app-template/src/views/product/find-in-store-section.tsx +60 -0
  185. package/app-template/src/views/product/index.ts +1 -0
  186. package/app-template/src/views/product/layout.tsx +6 -5
  187. package/app-template/src/views/product/misc-buttons.tsx +339 -25
  188. package/app-template/src/views/product/price-wrapper.tsx +3 -29
  189. package/app-template/src/views/product/product-actions.tsx +137 -8
  190. package/app-template/src/views/product/product-info-section.tsx +140 -0
  191. package/app-template/src/views/product/product-info.tsx +69 -31
  192. package/app-template/src/views/product/product-share.tsx +13 -8
  193. package/app-template/src/views/product/product-variants.tsx +2 -2
  194. package/app-template/src/views/product/quantity-section.tsx +73 -0
  195. package/app-template/src/views/product/sale-tag.tsx +10 -0
  196. package/app-template/src/views/product/share-section.tsx +357 -0
  197. package/app-template/src/views/product/slider.tsx +117 -79
  198. package/app-template/src/views/product/variant.tsx +69 -41
  199. package/app-template/src/views/product/variants-section.tsx +126 -0
  200. package/app-template/src/views/product-detail/constants.ts +272 -0
  201. package/app-template/src/views/product-detail/index.ts +10 -0
  202. package/app-template/src/views/product-detail/product-detail-registrar.tsx +616 -0
  203. package/app-template/src/views/product-item/index.tsx +119 -46
  204. package/app-template/src/views/register/index.tsx +14 -25
  205. package/app-template/src/views/share/index.tsx +9 -6
  206. package/app-template/src/views/widgets/home-hero-slider-content.tsx +41 -39
  207. package/app-template/src/widgets/flatpages/about-us/index.tsx +78 -0
  208. package/app-template/src/widgets/flatpages/blog-list/index.tsx +129 -0
  209. package/app-template/src/widgets/footer-app-banner.tsx +444 -0
  210. package/app-template/src/widgets/footer-bottom.tsx +127 -0
  211. package/app-template/src/widgets/footer-menu-compact.tsx +238 -0
  212. package/app-template/src/widgets/footer-menu-two.tsx +298 -0
  213. package/app-template/src/widgets/footer-social-client.tsx +251 -0
  214. package/app-template/src/widgets/footer-social.tsx +47 -16
  215. package/app-template/src/widgets/footer-subscription/footer-subscription-form.tsx +17 -14
  216. package/app-template/src/widgets/footer-subscription/index.tsx +183 -17
  217. package/app-template/src/widgets/footer-value-props.tsx +201 -0
  218. package/app-template/src/widgets/index.ts +7 -0
  219. package/app-template/src/widgets/schemas/about-us.json +46 -0
  220. package/app-template/src/widgets/schemas/blog-list.json +37 -0
  221. package/app-template/src/widgets/schemas/blog.json +29 -0
  222. package/app-template/tailwind.config.js +18 -2
  223. package/package.json +1 -1
@@ -0,0 +1,357 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect } from 'react';
4
+ import { Button, Icon } from '@theme/components';
5
+ // eslint-disable-next-line @akinon/projectzero/link-import
6
+ import Link from 'next/link';
7
+ import {
8
+ useProductDetail,
9
+ SHARE_SECTION_ID,
10
+ ShareLayoutType
11
+ } from '@theme/views/product-detail';
12
+ import clsx from 'clsx';
13
+
14
+ interface ShareItem {
15
+ href: string;
16
+ iconName: 'facebook' | 'twitter' | 'whatsapp';
17
+ iconSize?: number;
18
+ }
19
+
20
+ interface ShareSectionProps {
21
+ productName: string;
22
+ buttonText?: string;
23
+ items: ShareItem[];
24
+ className?: string;
25
+ }
26
+
27
+ export const ShareSection: React.FC<ShareSectionProps> = ({
28
+ productName,
29
+ buttonText,
30
+ items,
31
+ className
32
+ }) => {
33
+ const { isDesigner, selectedSectionId, shareStyles, shareProperties } =
34
+ useProductDetail();
35
+
36
+ const [open, setOpen] = useState(false);
37
+ const isSelected = selectedSectionId === SHARE_SECTION_ID;
38
+ const layout = shareProperties.layout || 'floating';
39
+ const showLabel = shareProperties.showLabel ?? true;
40
+
41
+ // Handle designer click for section selection
42
+ const handleDesignerClick = (e: React.MouseEvent) => {
43
+ if (isDesigner) {
44
+ e.stopPropagation();
45
+ window.parent?.postMessage(
46
+ {
47
+ type: 'SELECT_SECTION',
48
+ data: {
49
+ placeholderId: 'product-detail',
50
+ sectionId: SHARE_SECTION_ID
51
+ }
52
+ },
53
+ '*'
54
+ );
55
+ }
56
+ };
57
+
58
+ // Build CSS variables from shareStyles
59
+ const shareStyleVars: Record<string, string> = {};
60
+
61
+ // Button styles
62
+ if (shareStyles['button-font-size'])
63
+ shareStyleVars['--share-button-font-size'] =
64
+ shareStyles['button-font-size'];
65
+ if (shareStyles['button-font-weight'])
66
+ shareStyleVars['--share-button-font-weight'] =
67
+ shareStyles['button-font-weight'];
68
+ if (shareStyles['button-color'])
69
+ shareStyleVars['--share-button-color'] = shareStyles['button-color'];
70
+ if (shareStyles['button-background-color'])
71
+ shareStyleVars['--share-button-background-color'] =
72
+ shareStyles['button-background-color'];
73
+ if (shareStyles['button-border-color'])
74
+ shareStyleVars['--share-button-border-color'] =
75
+ shareStyles['button-border-color'];
76
+ if (shareStyles['button-border-width'])
77
+ shareStyleVars['--share-button-border-width'] =
78
+ shareStyles['button-border-width'];
79
+ if (shareStyles['button-border-radius'])
80
+ shareStyleVars['--share-button-border-radius'] =
81
+ shareStyles['button-border-radius'];
82
+ if (shareStyles['button-padding-x'])
83
+ shareStyleVars['--share-button-padding-x'] =
84
+ shareStyles['button-padding-x'];
85
+ if (shareStyles['button-padding-y'])
86
+ shareStyleVars['--share-button-padding-y'] =
87
+ shareStyles['button-padding-y'];
88
+
89
+ // Icon styles
90
+ if (shareStyles['icon-size'])
91
+ shareStyleVars['--share-icon-size'] = shareStyles['icon-size'];
92
+ if (shareStyles['icon-color'])
93
+ shareStyleVars['--share-icon-color'] = shareStyles['icon-color'];
94
+ if (shareStyles['icon-hover-color'])
95
+ shareStyleVars['--share-icon-hover-color'] =
96
+ shareStyles['icon-hover-color'];
97
+ if (shareStyles['icon-background-color'])
98
+ shareStyleVars['--share-icon-background-color'] =
99
+ shareStyles['icon-background-color'];
100
+ if (shareStyles['icon-hover-background-color'])
101
+ shareStyleVars['--share-icon-hover-background-color'] =
102
+ shareStyles['icon-hover-background-color'];
103
+ if (shareStyles['icon-border-radius'])
104
+ shareStyleVars['--share-icon-border-radius'] =
105
+ shareStyles['icon-border-radius'];
106
+ if (shareStyles['icon-spacing'])
107
+ shareStyleVars['--share-icon-spacing'] = shareStyles['icon-spacing'];
108
+
109
+ // Render different layouts
110
+ const renderFloatingLayout = () => (
111
+ <div className={clsx('flex items-center gap-4 border-t', className)}>
112
+ <Button
113
+ onClick={() => setOpen(!open)}
114
+ appearance="ghost"
115
+ className="text-base p-0 bg-transparent hover:bg-transparent hover:text-primary"
116
+ style={{
117
+ fontSize: 'var(--share-button-font-size, 16px)',
118
+ fontWeight: 'var(--share-button-font-weight, 400)',
119
+ color: 'var(--share-button-color, inherit)',
120
+ backgroundColor: 'var(--share-button-background-color, transparent)',
121
+ borderColor: 'var(--share-button-border-color, transparent)',
122
+ borderWidth: 'var(--share-button-border-width, 0)',
123
+ borderRadius: 'var(--share-button-border-radius, 0)',
124
+ paddingLeft: 'var(--share-button-padding-x, 0)',
125
+ paddingRight: 'var(--share-button-padding-x, 0)',
126
+ paddingTop: 'var(--share-button-padding-y, 0)',
127
+ paddingBottom: 'var(--share-button-padding-y, 0)'
128
+ }}
129
+ aria-label="Share"
130
+ >
131
+ <div className="flex items-center gap-2">
132
+ <Icon
133
+ name="share"
134
+ size={Number(shareStyles['icon-size']) || 24}
135
+ style={{
136
+ color: 'var(--share-icon-color, inherit)'
137
+ }}
138
+ />
139
+ {showLabel && buttonText && <span>{buttonText}</span>}
140
+ </div>
141
+ </Button>
142
+
143
+ <div
144
+ className={clsx(
145
+ 'share-group flex transition-max-width ease-linear duration-300',
146
+ open
147
+ ? 'opacity-100 max-w-xs visible pointer-events-auto'
148
+ : 'opacity-0 max-w-0 invisible pointer-events-none'
149
+ )}
150
+ style={{
151
+ gap: 'var(--share-icon-spacing, 0)'
152
+ }}
153
+ >
154
+ {items.map((item, index) => (
155
+ <Link
156
+ key={index}
157
+ target="_blank"
158
+ href={item.href}
159
+ className="p-4 h-auto flex items-center hover:bg-gray-100"
160
+ style={{
161
+ borderRadius: 'var(--share-icon-border-radius, 0)',
162
+ backgroundColor: 'var(--share-icon-background-color, transparent)'
163
+ }}
164
+ >
165
+ <Icon
166
+ name={item.iconName}
167
+ size={Number(shareStyles['icon-size']) || item.iconSize || 12}
168
+ style={{
169
+ color: 'var(--share-icon-color, inherit)'
170
+ }}
171
+ />
172
+ </Link>
173
+ ))}
174
+ </div>
175
+ </div>
176
+ );
177
+
178
+ const renderInlineHorizontalLayout = () => (
179
+ <div
180
+ className={clsx('flex items-center flex-wrap', className)}
181
+ style={{
182
+ gap: 'var(--share-icon-spacing, 12px)'
183
+ }}
184
+ >
185
+ {showLabel && buttonText && (
186
+ <span
187
+ style={{
188
+ fontSize: 'var(--share-button-font-size, 16px)',
189
+ fontWeight: 'var(--share-button-font-weight, 400)',
190
+ color: 'var(--share-button-color, inherit)'
191
+ }}
192
+ >
193
+ {buttonText}:
194
+ </span>
195
+ )}
196
+ {items.map((item, index) => (
197
+ <Link
198
+ key={index}
199
+ target="_blank"
200
+ href={item.href}
201
+ className="inline-flex items-center justify-center transition-colors"
202
+ style={{
203
+ padding: 'var(--share-button-padding-y, 8px)',
204
+ borderRadius: 'var(--share-icon-border-radius, 4px)',
205
+ backgroundColor: 'var(--share-icon-background-color, #f3f4f6)',
206
+ color: 'var(--share-icon-color, inherit)'
207
+ }}
208
+ >
209
+ <Icon
210
+ name={item.iconName}
211
+ size={Number(shareStyles['icon-size']) || item.iconSize || 20}
212
+ />
213
+ </Link>
214
+ ))}
215
+ </div>
216
+ );
217
+
218
+ const renderInlineVerticalLayout = () => (
219
+ <div
220
+ className={clsx('flex flex-col', className)}
221
+ style={{
222
+ gap: 'var(--share-icon-spacing, 12px)'
223
+ }}
224
+ >
225
+ {showLabel && buttonText && (
226
+ <span
227
+ style={{
228
+ fontSize: 'var(--share-button-font-size, 16px)',
229
+ fontWeight: 'var(--share-button-font-weight, 400)',
230
+ color: 'var(--share-button-color, inherit)'
231
+ }}
232
+ >
233
+ {buttonText}
234
+ </span>
235
+ )}
236
+ <div className="flex flex-col gap-2">
237
+ {items.map((item, index) => (
238
+ <Link
239
+ key={index}
240
+ target="_blank"
241
+ href={item.href}
242
+ className="inline-flex items-center justify-center transition-colors"
243
+ style={{
244
+ padding: 'var(--share-button-padding-y, 8px)',
245
+ borderRadius: 'var(--share-icon-border-radius, 4px)',
246
+ backgroundColor: 'var(--share-icon-background-color, #f3f4f6)',
247
+ color: 'var(--share-icon-color, inherit)',
248
+ width: 'fit-content'
249
+ }}
250
+ >
251
+ <Icon
252
+ name={item.iconName}
253
+ size={Number(shareStyles['icon-size']) || item.iconSize || 20}
254
+ />
255
+ </Link>
256
+ ))}
257
+ </div>
258
+ </div>
259
+ );
260
+
261
+ const renderIconOnlyLayout = () => (
262
+ <div
263
+ className={clsx('flex items-center flex-wrap', className)}
264
+ style={{
265
+ gap: 'var(--share-icon-spacing, 8px)'
266
+ }}
267
+ >
268
+ {items.map((item, index) => (
269
+ <Link
270
+ key={index}
271
+ target="_blank"
272
+ href={item.href}
273
+ className="inline-flex items-center justify-center transition-colors"
274
+ style={{
275
+ padding: 'var(--share-button-padding-y, 8px)',
276
+ borderRadius: 'var(--share-icon-border-radius, 50%)',
277
+ backgroundColor: 'var(--share-icon-background-color, #f3f4f6)',
278
+ color: 'var(--share-icon-color, inherit)'
279
+ }}
280
+ >
281
+ <Icon
282
+ name={item.iconName}
283
+ size={Number(shareStyles['icon-size']) || item.iconSize || 18}
284
+ />
285
+ </Link>
286
+ ))}
287
+ </div>
288
+ );
289
+
290
+ const renderCompactLayout = () => (
291
+ <div
292
+ className={clsx('flex items-center', className)}
293
+ style={{
294
+ gap: 'var(--share-icon-spacing, 6px)'
295
+ }}
296
+ >
297
+ {showLabel && buttonText && (
298
+ <span
299
+ style={{
300
+ fontSize: 'var(--share-button-font-size, 14px)',
301
+ fontWeight: 'var(--share-button-font-weight, 400)',
302
+ color: 'var(--share-button-color, inherit)'
303
+ }}
304
+ >
305
+ {buttonText}
306
+ </span>
307
+ )}
308
+ {items.map((item, index) => (
309
+ <Link
310
+ key={index}
311
+ target="_blank"
312
+ href={item.href}
313
+ className="inline-flex items-center justify-center transition-colors"
314
+ style={{
315
+ padding: 'var(--share-button-padding-y, 4px)',
316
+ borderRadius: 'var(--share-icon-border-radius, 4px)',
317
+ backgroundColor: 'var(--share-icon-background-color, transparent)',
318
+ color: 'var(--share-icon-color, inherit)'
319
+ }}
320
+ >
321
+ <Icon
322
+ name={item.iconName}
323
+ size={Number(shareStyles['icon-size']) || item.iconSize || 16}
324
+ />
325
+ </Link>
326
+ ))}
327
+ </div>
328
+ );
329
+
330
+ const renderLayout = () => {
331
+ switch (layout) {
332
+ case 'inline-horizontal':
333
+ return renderInlineHorizontalLayout();
334
+ case 'inline-vertical':
335
+ return renderInlineVerticalLayout();
336
+ case 'icon-only':
337
+ return renderIconOnlyLayout();
338
+ case 'compact':
339
+ return renderCompactLayout();
340
+ case 'floating':
341
+ default:
342
+ return renderFloatingLayout();
343
+ }
344
+ };
345
+
346
+ return (
347
+ <div
348
+ style={shareStyleVars}
349
+ className={clsx('relative', {
350
+ 'ring-2 ring-blue-500 ring-offset-2': isDesigner && isSelected
351
+ })}
352
+ onClick={handleDesignerClick}
353
+ >
354
+ {renderLayout()}
355
+ </div>
356
+ );
357
+ };
@@ -1,12 +1,11 @@
1
1
  'use client';
2
2
 
3
- import React, { useState, useRef } from 'react';
3
+ import React, { useRef, useState, useCallback } from 'react';
4
4
  import { CarouselCore } from '@theme/components/carousel-core';
5
- import { Icon } from '@theme/components';
6
5
  import { Product } from '@akinon/next/types';
7
6
  import { Image } from '@akinon/next/components/image';
8
- import useFavButton from '../../hooks/use-fav-button';
9
- import { twMerge } from 'tailwind-merge';
7
+ import { Icon, Button } from '@theme/components';
8
+ import CustomButtonGroup from './custom-button-group';
10
9
  import PluginModule, { Component } from '@akinon/next/components/plugin-module';
11
10
 
12
11
  type ProductSliderItem = {
@@ -14,81 +13,32 @@ type ProductSliderItem = {
14
13
  };
15
14
 
16
15
  export default function ProductInfoSlider({ product }: ProductSliderItem) {
17
- const { FavButton } = useFavButton(product.pk);
18
16
  const carouselRef = useRef(null);
17
+ const [zoomedImage, setZoomedImage] = useState<string | null>(null);
18
+ const handleOpenZoom = useCallback((src: string) => setZoomedImage(src), []);
19
+ const handleCloseZoom = useCallback(() => setZoomedImage(null), []);
20
+ const productImages = product?.productimage_set ?? [];
19
21
  const [activeIndex, setActiveIndex] = useState(0);
20
22
 
21
- const goToPrev = () => {
22
- const newIndex =
23
- activeIndex === 0
24
- ? product?.productimage_set?.length - 1
25
- : activeIndex - 1;
26
- setActiveIndex(newIndex);
27
- carouselRef.current?.previous();
28
- };
23
+ const firstImage = productImages[0];
24
+ const remainingImages = productImages.slice(1);
25
+ const smallImagePairs = [];
26
+ let trailingLargeImage = null;
29
27
 
30
- const goToNext = () => {
31
- const newIndex =
32
- activeIndex === product?.productimage_set?.length - 1
33
- ? 0
34
- : activeIndex + 1;
35
- setActiveIndex(newIndex);
36
- carouselRef.current?.next();
37
- };
38
-
39
- const handleThumbnailClick = (index) => {
40
- setActiveIndex(index);
41
- carouselRef.current?.goToSlide(index);
42
- };
28
+ for (let i = 0; i < remainingImages.length; i += 2) {
29
+ if (remainingImages[i + 1]) {
30
+ smallImagePairs.push([remainingImages[i], remainingImages[i + 1]]);
31
+ } else {
32
+ trailingLargeImage = remainingImages[i];
33
+ }
34
+ }
43
35
 
44
36
  return (
45
- <div className="lg:grid lg:grid-cols-6">
46
- <div className="lg:col-span-1">
47
- <div className="flex flex-col items-center justify-center md:mr-[6px]">
48
- <button
49
- onClick={goToPrev}
50
- className={twMerge(
51
- 'hidden justify-center p-2 mb-3 border border-gray-100 rounded-full cursor-pointer lg:block',
52
- [activeIndex === 0 && 'cursor-not-allowed opacity-45']
53
- )}
54
- disabled={activeIndex === 0}
55
- >
56
- <Icon name="chevron-up" size={15} className="fill-[#000000]" />
57
- </button>
58
- <div className="hidden flex-col items-center overflow-scroll w-[80px] max-h-[620px] lg:block">
59
- {product?.productimage_set?.map((item, index) => (
60
- <Image
61
- key={index}
62
- src={item.image}
63
- alt={`Thumbnail ${index}`}
64
- width={80}
65
- height={128}
66
- className={twMerge('cursor-pointer', [
67
- activeIndex === index && 'border-2 border-primary'
68
- ])}
69
- onClick={() => handleThumbnailClick(index)}
70
- />
71
- ))}
72
- </div>
73
- <button
74
- onClick={goToNext}
75
- className={twMerge(
76
- 'hidden justify-center p-2 mt-3 border border-gray-100 rounded-full cursor-pointer lg:block',
77
- [
78
- activeIndex === product.productimage_set.length - 1 &&
79
- 'cursor-not-allowed opacity-45'
80
- ]
81
- )}
82
- disabled={activeIndex === product.productimage_set.length - 1}
83
- >
84
- <Icon name="chevron-down" size={15} className="fill-[#000000]" />
85
- </button>
37
+ <>
38
+ <div className="relative lg:hidden">
39
+ <div className="absolute left-6 top-6 z-[20] w-8 h-8 flex items-center justify-center rounded-full bg-white border border-gray-30">
40
+ <Icon name="zoom" size={11} className="text-primary" />
86
41
  </div>
87
- </div>
88
-
89
- <div className="relative lg:col-span-5">
90
- <FavButton className="absolute right-8 top-6 z-[20] sm:hidden" />
91
-
92
42
  <PluginModule
93
43
  component={Component.ProductImageSearchFeature}
94
44
  props={{
@@ -96,7 +46,8 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
96
46
  activeIndex,
97
47
  showResetButton: true,
98
48
  enableTextSearch: true,
99
- isEnabled: true
49
+ isEnabled: true,
50
+ className: 'right-2 top-6 absolute z-[30] w-auto px-4 text-xs mt-0'
100
51
  }}
101
52
  />
102
53
 
@@ -108,7 +59,6 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
108
59
  'sm:hidden absolute bottom-[70px] right-[5px] z-[30] w-auto px-4 text-xs mt-0'
109
60
  }}
110
61
  />
111
-
112
62
  <CarouselCore
113
63
  responsive={{
114
64
  all: {
@@ -117,29 +67,117 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
117
67
  }
118
68
  }}
119
69
  arrows={false}
120
- swipeable={true}
70
+ swipeable
121
71
  ref={carouselRef}
122
72
  afterChange={(previousSlide, { currentSlide }) => {
123
73
  setActiveIndex(currentSlide);
124
74
  }}
125
- containerAspectRatio={{ mobile: 520 / 798, desktop: 484 / 726 }}
75
+ containerAspectRatio={{ mobile: 1, desktop: 1 }}
76
+ renderButtonGroupOutside
77
+ customButtonGroup={<CustomButtonGroup />}
126
78
  >
127
- {product?.productimage_set?.map((item, i) => (
79
+ {productImages.map((item, i) => (
128
80
  <Image
129
81
  key={i}
130
82
  src={item.image}
131
83
  alt={product.name}
132
84
  draggable={false}
133
- aspectRatio={484 / 726}
85
+ aspectRatio={1}
134
86
  sizes="(min-width: 425px) 512px,
135
87
  (min-width: 601px) 576px,
136
88
  (min-width: 768px) 336px,
137
89
  (min-width: 1024px) 484px, 368px"
138
90
  fill
91
+ className="cursor-zoom-in"
92
+ onClick={() => handleOpenZoom(item.image)}
139
93
  />
140
94
  ))}
141
95
  </CarouselCore>
142
96
  </div>
143
- </div>
97
+ <div className="hidden lg:flex lg:flex-col lg:gap-2.5">
98
+ {firstImage && (
99
+ <div className="relative w-full overflow-hidden bg-gray-50 aspect-square">
100
+ <div className="absolute left-6 top-6 z-[20] w-8 h-8 flex items-center justify-center rounded-full bg-white border border-gray-30">
101
+ <Icon name="zoom" size={11} className="text-primary" />
102
+ </div>
103
+
104
+ <Image
105
+ src={firstImage.image}
106
+ alt={`${product.name} 1`}
107
+ draggable={false}
108
+ fill
109
+ aspectRatio={1}
110
+ sizes="(min-width: 1024px) 50vw, 90vw"
111
+ imageClassName="object-cover cursor-zoom-in"
112
+ onClick={() => handleOpenZoom(firstImage.image)}
113
+ />
114
+ </div>
115
+ )}
116
+ {smallImagePairs.map((pair, pairIndex) => (
117
+ <div key={pairIndex} className="grid grid-cols-2 gap-2.5">
118
+ {pair.map((item, itemIndex) => (
119
+ <div
120
+ key={itemIndex}
121
+ className="relative w-full overflow-hidden bg-gray-50 aspect-square"
122
+ >
123
+ <Image
124
+ src={item.image}
125
+ alt={`${product.name} ${pairIndex * 2 + itemIndex + 2}`}
126
+ draggable={false}
127
+ fill
128
+ aspectRatio={1}
129
+ sizes="(min-width: 1024px) 25vw, 45vw"
130
+ imageClassName="object-cover cursor-zoom-in"
131
+ onClick={() => handleOpenZoom(item.image)}
132
+ />
133
+ </div>
134
+ ))}
135
+ </div>
136
+ ))}
137
+ {trailingLargeImage && (
138
+ <div className="relative w-full overflow-hidden bg-gray-50 aspect-square">
139
+ <Image
140
+ src={trailingLargeImage.image}
141
+ alt={`${product.name} ${productImages.length}`}
142
+ draggable={false}
143
+ fill
144
+ aspectRatio={1}
145
+ sizes="(min-width: 1024px) 50vw, 90vw"
146
+ imageClassName="object-cover cursor-zoom-in"
147
+ onClick={() => handleOpenZoom(trailingLargeImage.image)}
148
+ />
149
+ </div>
150
+ )}
151
+ </div>
152
+ {zoomedImage && (
153
+ <div
154
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4"
155
+ onClick={handleCloseZoom}
156
+ >
157
+ <div
158
+ className="relative w-full max-w-3xl aspect-square"
159
+ onClick={(event) => event.stopPropagation()}
160
+ >
161
+ <Button
162
+ type="button"
163
+ aria-label="Close zoomed image"
164
+ className="absolute right-3 top-3 z-10 rounded-full bg-white/90 px-3 py-1 text-sm font-medium text-gray-900 shadow cursor-pointer"
165
+ onClick={handleCloseZoom}
166
+ >
167
+ ×
168
+ </Button>
169
+ <Image
170
+ src={zoomedImage}
171
+ alt={`${product.name} zoomed`}
172
+ draggable={false}
173
+ fill
174
+ aspectRatio={1}
175
+ sizes="100vw"
176
+ imageClassName="object-contain"
177
+ />
178
+ </div>
179
+ </div>
180
+ )}
181
+ </>
144
182
  );
145
183
  }