@thorprovider/blocks 0.1.3 → 2.0.0

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 (178) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +228 -68
  3. package/docs/standards/data-contracts.md +13 -2
  4. package/package.json +26 -10
  5. package/scripts/fix-esm-extensions.mjs +40 -0
  6. package/src/blocks/application/login-forms/login-page.test.tsx +190 -0
  7. package/src/blocks/application/login-forms/login-page.tsx +240 -0
  8. package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +6 -8
  9. package/src/blocks/application/signup-forms/signup-page-1.tsx +127 -129
  10. package/src/blocks/application/verification/verify-1.tsx +2 -2
  11. package/src/blocks/application/viewport-ownership.test.tsx +41 -0
  12. package/src/blocks/e-commerce/category-filters/category-listing-filter-fields.tsx +218 -0
  13. package/src/blocks/e-commerce/category-filters/{category-filter-2.test.tsx → category-listing.test.tsx} +287 -199
  14. package/src/blocks/e-commerce/category-filters/category-listing.tsx +934 -0
  15. package/src/blocks/e-commerce/checkout-forms/checkout-contact-step.tsx +149 -0
  16. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-country-field.tsx → checkout-country-field.tsx} +10 -8
  17. package/src/blocks/e-commerce/checkout-forms/checkout-order-summary.tsx +230 -0
  18. package/src/blocks/e-commerce/checkout-forms/checkout-payment-step.tsx +100 -0
  19. package/src/blocks/e-commerce/checkout-forms/checkout-progress.tsx +62 -0
  20. package/src/blocks/e-commerce/checkout-forms/checkout-review-step.tsx +59 -0
  21. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-shipping-step.tsx → checkout-shipping-step.tsx} +65 -41
  22. package/src/blocks/e-commerce/checkout-forms/checkout.test.tsx +478 -0
  23. package/src/blocks/e-commerce/checkout-forms/checkout.tsx +550 -0
  24. package/src/blocks/e-commerce/navigation/storefront-navbar-account-menu.test.tsx +56 -0
  25. package/src/blocks/{marketing/navbars/navbar-4-account-menu.tsx → e-commerce/navigation/storefront-navbar-account-menu.tsx} +3 -3
  26. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.test.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.test.tsx} +22 -22
  27. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.tsx} +3 -3
  28. package/src/blocks/{marketing/navbars/navbar-4-mobile-nav.tsx → e-commerce/navigation/storefront-navbar-mobile-nav.tsx} +4 -12
  29. package/src/blocks/{marketing/navbars/navbar-4-search-bar.tsx → e-commerce/navigation/storefront-navbar-search-bar.tsx} +3 -3
  30. package/src/blocks/{marketing/navbars/navbar-4.test.tsx → e-commerce/navigation/storefront-navbar.test.tsx} +67 -36
  31. package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +43 -25
  32. package/src/blocks/e-commerce/product-categories/category-card-grid.test.tsx +122 -0
  33. package/src/blocks/e-commerce/product-categories/category-card.test.tsx +68 -0
  34. package/src/blocks/e-commerce/product-categories/category-card.tsx +172 -0
  35. package/src/blocks/e-commerce/product-categories/category-grid.test.tsx +67 -0
  36. package/src/blocks/e-commerce/product-categories/category-shared.tsx +109 -0
  37. package/src/blocks/e-commerce/product-categories/product-category-grid.tsx +170 -0
  38. package/src/blocks/e-commerce/product-categories/product-category.test.tsx +266 -0
  39. package/src/blocks/e-commerce/product-categories/product-category.tsx +462 -0
  40. package/src/blocks/e-commerce/product-list/product-card.test.tsx +263 -0
  41. package/src/blocks/e-commerce/product-list/product-card.tsx +447 -0
  42. package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
  43. package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
  44. package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
  45. package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
  46. package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
  47. package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
  48. package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
  49. package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
  50. package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
  51. package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
  52. package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +119 -85
  53. package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
  54. package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
  55. package/src/blocks/marketing/features/feature.test.tsx +153 -0
  56. package/src/blocks/marketing/features/feature.tsx +195 -0
  57. package/src/blocks/marketing/footers/footer-section-accessibility.test.tsx +118 -0
  58. package/src/blocks/marketing/footers/footer-section-actions.test.tsx +131 -0
  59. package/src/blocks/marketing/footers/footer-section-data.test.tsx +127 -0
  60. package/src/blocks/marketing/footers/footer-section-native-links.test.tsx +100 -0
  61. package/src/blocks/marketing/footers/footer-section-public-api.test.tsx +11 -0
  62. package/src/blocks/marketing/footers/footer-section.test.tsx +46 -0
  63. package/src/blocks/marketing/footers/footer-section.tsx +532 -0
  64. package/src/blocks/marketing/navbars/navbar-public-api.test.tsx +57 -0
  65. package/src/blocks/marketing/navbars/navbar.mega.test.tsx +149 -0
  66. package/src/blocks/marketing/navbars/navbar.test.tsx +155 -0
  67. package/src/blocks/marketing/navbars/navbar.tsx +735 -0
  68. package/src/blocks/marketing/testimonials/testimonial-section.test.tsx +171 -0
  69. package/src/blocks/marketing/testimonials/testimonial-section.tsx +237 -0
  70. package/src/index.ts +156 -128
  71. package/src/lib/contracts-curated.test.ts +47 -29
  72. package/src/lib/contracts.test.ts +198 -2
  73. package/src/lib/contracts.ts +410 -16
  74. package/src/ui/button-group.tsx +1 -1
  75. package/src/ui/carousel.tsx +1 -1
  76. package/src/ui/dialog.tsx +1 -1
  77. package/src/ui/field.tsx +2 -2
  78. package/src/ui/input-group.tsx +3 -3
  79. package/src/ui/pagination.tsx +1 -1
  80. package/src/ui/sidebar.tsx +7 -7
  81. package/src/ui/toggle-group.tsx +1 -1
  82. package/tsconfig.build.json +12 -0
  83. package/tsup.config.ts +11 -7
  84. package/.turbo/turbo-build.log +0 -24
  85. package/.turbo/turbo-test.log +0 -47
  86. package/.turbo/turbo-type-check.log +0 -0
  87. package/dist/.tsbuildinfo +0 -1
  88. package/dist/contracts.d.mts +0 -1732
  89. package/dist/contracts.d.ts +0 -1732
  90. package/dist/contracts.js +0 -423
  91. package/dist/contracts.js.map +0 -1
  92. package/dist/contracts.mjs +0 -365
  93. package/dist/contracts.mjs.map +0 -1
  94. package/dist/index.d.mts +0 -3068
  95. package/dist/index.d.ts +0 -3068
  96. package/dist/index.js +0 -17834
  97. package/dist/index.js.map +0 -1
  98. package/dist/index.mjs +0 -17740
  99. package/dist/index.mjs.map +0 -1
  100. package/src/blocks/application/login-forms/login-page-1.tsx +0 -246
  101. package/src/blocks/application/login-forms/login-page-2.tsx +0 -207
  102. package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +0 -260
  103. package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +0 -222
  104. package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +0 -921
  105. package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +0 -57
  106. package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +0 -124
  107. package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +0 -170
  108. package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +0 -52
  109. package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +0 -57
  110. package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +0 -68
  111. package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +0 -298
  112. package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +0 -47
  113. package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +0 -9
  114. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +0 -96
  115. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +0 -252
  116. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +0 -188
  117. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +0 -36
  118. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +0 -216
  119. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +0 -338
  120. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +0 -130
  121. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +0 -175
  122. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +0 -190
  123. package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +0 -312
  124. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +0 -91
  125. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +0 -110
  126. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +0 -137
  127. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +0 -68
  128. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +0 -79
  129. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +0 -172
  130. package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +0 -392
  131. package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
  132. package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
  133. package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
  134. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
  135. package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
  136. package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
  137. package/src/blocks/e-commerce/product-list/product-card-1.test.tsx +0 -88
  138. package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -192
  139. package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
  140. package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
  141. package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
  142. package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
  143. package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
  144. package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
  145. package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
  146. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
  147. package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
  148. package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
  149. package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
  150. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
  151. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
  152. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
  153. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
  154. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
  155. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
  156. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
  157. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
  158. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
  159. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -229
  160. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -72
  161. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
  162. package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
  163. package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
  164. package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
  165. package/src/blocks/marketing/features/feature-1.tsx +0 -97
  166. package/src/blocks/marketing/features/feature-2.tsx +0 -82
  167. package/src/blocks/marketing/footers/footer-section-1.test.tsx +0 -117
  168. package/src/blocks/marketing/footers/footer-section-1.tsx +0 -178
  169. package/src/blocks/marketing/footers/footer-section-3.tsx +0 -192
  170. package/src/blocks/marketing/footers/footer-section-4.tsx +0 -355
  171. package/src/blocks/marketing/navbars/navbar-1.tsx +0 -267
  172. package/src/blocks/marketing/navbars/navbar-2.tsx +0 -351
  173. package/src/blocks/marketing/navbars/navbar-3.tsx +0 -303
  174. package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +0 -38
  175. package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +0 -177
  176. package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +0 -219
  177. package/src/blocks/marketing/testimonials/testimonial-1.tsx +0 -141
  178. package/src/blocks/marketing/testimonials/testimonial-2.tsx +0 -108
@@ -1,6 +1,6 @@
1
1
  import { renderToStaticMarkup } from 'react-dom/server'
2
2
  import { describe, expect, it } from 'vitest'
3
- import { Navbar4CartSheet, type CartItem } from './navbar-4-cart-sheet'
3
+ import { StorefrontNavbarCartSheet, type CartItem } from './storefront-navbar-cart-sheet'
4
4
 
5
5
  const items: CartItem[] = [
6
6
  { id: 'line_1', name: 'Taladro', variant: 'Rojo', price: 45, qty: 2, image: 'https://cdn.example/t.jpg' },
@@ -11,10 +11,10 @@ const formatMoney = (money: { amount: string; currencyCode: string }) =>
11
11
 
12
12
  const noop = () => {}
13
13
 
14
- describe('Navbar4CartSheet — authoritative subtotal', () => {
14
+ describe('StorefrontNavbarCartSheet — authoritative subtotal', () => {
15
15
  it('never renders a fake zero for a supplied but absent/malformed subtotal', () => {
16
16
  const html = renderToStaticMarkup(
17
- <Navbar4CartSheet
17
+ <StorefrontNavbarCartSheet
18
18
  items={items}
19
19
  onQtyChange={noop}
20
20
  onRemove={noop}
@@ -28,7 +28,7 @@ describe('Navbar4CartSheet — authoritative subtotal', () => {
28
28
 
29
29
  it('preserves a legitimate zero subtotal', () => {
30
30
  const html = renderToStaticMarkup(
31
- <Navbar4CartSheet
31
+ <StorefrontNavbarCartSheet
32
32
  items={items}
33
33
  onQtyChange={noop}
34
34
  onRemove={noop}
@@ -42,7 +42,7 @@ describe('Navbar4CartSheet — authoritative subtotal', () => {
42
42
 
43
43
  it('falls back to the legacy local subtotal only when none is supplied', () => {
44
44
  const html = renderToStaticMarkup(
45
- <Navbar4CartSheet
45
+ <StorefrontNavbarCartSheet
46
46
  items={items}
47
47
  onQtyChange={noop}
48
48
  onRemove={noop}
@@ -53,10 +53,10 @@ describe('Navbar4CartSheet — authoritative subtotal', () => {
53
53
  })
54
54
  })
55
55
 
56
- describe('Navbar4CartSheet — canonical line amounts', () => {
56
+ describe('StorefrontNavbarCartSheet — canonical line amounts', () => {
57
57
  it('renders the canonical backend line amount in its currency, not price times qty', () => {
58
58
  const html = renderToStaticMarkup(
59
- <Navbar4CartSheet
59
+ <StorefrontNavbarCartSheet
60
60
  items={[{ ...items[0], price: 0, lineTotal: { amount: '90.00', currencyCode: 'EUR' } }]}
61
61
  onQtyChange={noop}
62
62
  onRemove={noop}
@@ -70,7 +70,7 @@ describe('Navbar4CartSheet — canonical line amounts', () => {
70
70
 
71
71
  it('never falls back to a numeric amount when a canonical line amount is missing', () => {
72
72
  const html = renderToStaticMarkup(
73
- <Navbar4CartSheet
73
+ <StorefrontNavbarCartSheet
74
74
  items={[{ ...items[0], price: 45, lineTotal: undefined }]}
75
75
  onQtyChange={noop}
76
76
  onRemove={noop}
@@ -84,7 +84,7 @@ describe('Navbar4CartSheet — canonical line amounts', () => {
84
84
 
85
85
  it('shows the unavailable label for a malformed canonical line amount', () => {
86
86
  const html = renderToStaticMarkup(
87
- <Navbar4CartSheet
87
+ <StorefrontNavbarCartSheet
88
88
  items={[{ ...items[0], lineTotal: { amount: '', currencyCode: 'USD' } }]}
89
89
  onQtyChange={noop}
90
90
  onRemove={noop}
@@ -98,7 +98,7 @@ describe('Navbar4CartSheet — canonical line amounts', () => {
98
98
 
99
99
  it('preserves legacy numeric rendering when canonical mode is omitted', () => {
100
100
  const html = renderToStaticMarkup(
101
- <Navbar4CartSheet
101
+ <StorefrontNavbarCartSheet
102
102
  items={[{ ...items[0], lineTotal: { amount: '90.00', currencyCode: 'EUR' } }]}
103
103
  onQtyChange={noop}
104
104
  onRemove={noop}
@@ -110,10 +110,10 @@ describe('Navbar4CartSheet — canonical line amounts', () => {
110
110
  })
111
111
  })
112
112
 
113
- describe('Navbar4CartSheet — read/mutation lifecycle', () => {
113
+ describe('StorefrontNavbarCartSheet — read/mutation lifecycle', () => {
114
114
  it('shows a pending state instead of claiming an empty cart', () => {
115
115
  const html = renderToStaticMarkup(
116
- <Navbar4CartSheet items={[]} onQtyChange={noop} onRemove={noop} state='pending' />,
116
+ <StorefrontNavbarCartSheet items={[]} onQtyChange={noop} onRemove={noop} state='pending' />,
117
117
  )
118
118
  expect(html).toContain('Actualizando el carrito')
119
119
  expect(html).not.toContain('Your cart is empty')
@@ -121,7 +121,7 @@ describe('Navbar4CartSheet — read/mutation lifecycle', () => {
121
121
 
122
122
  it('shows a recoverable error state with its retry action', () => {
123
123
  const html = renderToStaticMarkup(
124
- <Navbar4CartSheet
124
+ <StorefrontNavbarCartSheet
125
125
  items={items}
126
126
  onQtyChange={noop}
127
127
  onRemove={noop}
@@ -138,17 +138,17 @@ describe('Navbar4CartSheet — read/mutation lifecycle', () => {
138
138
 
139
139
  it('disables mutation controls while pending', () => {
140
140
  const html = renderToStaticMarkup(
141
- <Navbar4CartSheet items={items} onQtyChange={noop} onRemove={noop} state='pending' />,
141
+ <StorefrontNavbarCartSheet items={items} onQtyChange={noop} onRemove={noop} state='pending' />,
142
142
  )
143
143
  expect(html).toContain('aria-label="Increase quantity, Taladro"')
144
144
  expect(html).toContain('disabled')
145
145
  })
146
146
  })
147
147
 
148
- describe('Navbar4CartSheet — action availability', () => {
148
+ describe('StorefrontNavbarCartSheet — action availability', () => {
149
149
  it('suppresses the checkout action when checkout is not installed', () => {
150
150
  const html = renderToStaticMarkup(
151
- <Navbar4CartSheet
151
+ <StorefrontNavbarCartSheet
152
152
  items={items}
153
153
  onQtyChange={noop}
154
154
  onRemove={noop}
@@ -162,7 +162,7 @@ describe('Navbar4CartSheet — action availability', () => {
162
162
 
163
163
  it('disables the checkout action while the cart is not ready', () => {
164
164
  const html = renderToStaticMarkup(
165
- <Navbar4CartSheet items={items} onQtyChange={noop} onRemove={noop} checkoutDisabled onCheckout={noop} />,
165
+ <StorefrontNavbarCartSheet items={items} onQtyChange={noop} onRemove={noop} checkoutDisabled onCheckout={noop} />,
166
166
  )
167
167
  expect(html).toContain('>Checkout</button>')
168
168
  expect(html).toMatch(/disabled[^>]*>Checkout<\/button>/)
@@ -170,7 +170,7 @@ describe('Navbar4CartSheet — action availability', () => {
170
170
 
171
171
  it('suppresses the review action when the cart view is not installed', () => {
172
172
  const html = renderToStaticMarkup(
173
- <Navbar4CartSheet
173
+ <StorefrontNavbarCartSheet
174
174
  items={items}
175
175
  onQtyChange={noop}
176
176
  onRemove={noop}
@@ -182,10 +182,10 @@ describe('Navbar4CartSheet — action availability', () => {
182
182
  })
183
183
  })
184
184
 
185
- describe('Navbar4CartSheet — accessibility', () => {
185
+ describe('StorefrontNavbarCartSheet — accessibility', () => {
186
186
  it('names the line mutation controls', () => {
187
187
  const html = renderToStaticMarkup(
188
- <Navbar4CartSheet items={items} onQtyChange={noop} onRemove={noop} />,
188
+ <StorefrontNavbarCartSheet items={items} onQtyChange={noop} onRemove={noop} />,
189
189
  )
190
190
  expect(html).toMatch(/aria-label="[^"]*(Increase|Decrease|Aumentar|Disminuir)[^"]*"/)
191
191
  expect(html).toMatch(/aria-label="[^"]*(Remove|Quitar)[^"]*"/)
@@ -193,11 +193,11 @@ describe('Navbar4CartSheet — accessibility', () => {
193
193
 
194
194
  it('announces pending reads politely and errors as an alert', () => {
195
195
  const pending = renderToStaticMarkup(
196
- <Navbar4CartSheet items={[]} onQtyChange={noop} onRemove={noop} state='pending' />,
196
+ <StorefrontNavbarCartSheet items={[]} onQtyChange={noop} onRemove={noop} state='pending' />,
197
197
  )
198
198
  expect(pending).toMatch(/role="status"[^>]*aria-live="polite"/)
199
199
  const error = renderToStaticMarkup(
200
- <Navbar4CartSheet items={items} onQtyChange={noop} onRemove={noop} state='error' />,
200
+ <StorefrontNavbarCartSheet items={items} onQtyChange={noop} onRemove={noop} state='error' />,
201
201
  )
202
202
  expect(error).toMatch(/role="alert"/)
203
203
  })
@@ -37,7 +37,7 @@ export interface CartItem {
37
37
 
38
38
  export type CartSheetState = 'idle' | 'pending' | 'error'
39
39
 
40
- export interface Navbar4CartSheetProps {
40
+ export interface StorefrontNavbarCartSheetProps {
41
41
  items: CartItem[]
42
42
  /**
43
43
  * Quantity DELTA for a backend cart line-item id. The boundary converts the
@@ -104,7 +104,7 @@ const revealHidden = { opacity: 0, y: 12, filter: 'blur(3px)' }
104
104
  const revealShown = { opacity: 1, y: 0, filter: 'blur(0px)' }
105
105
  const revealExit = { opacity: 0, y: 0, filter: 'blur(0px)', transition: { duration: 0.2, ease: 'easeOut' as const } }
106
106
 
107
- export function Navbar4CartSheet({
107
+ export function StorefrontNavbarCartSheet({
108
108
  items,
109
109
  onQtyChange,
110
110
  onRemove,
@@ -124,7 +124,7 @@ export function Navbar4CartSheet({
124
124
  checkoutUnavailable = false,
125
125
  checkoutDisabled = false,
126
126
  viewCartUnavailable = false,
127
- }: Navbar4CartSheetProps) {
127
+ }: StorefrontNavbarCartSheetProps) {
128
128
  const reduceMotion = useReducedMotion()
129
129
  const format = formatPrice ?? (value => usdFormatter.format(value))
130
130
  const localSubtotal = items.reduce((total, item) => total + item.price * item.qty, 0)
@@ -2,33 +2,25 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '..
2
2
  import { Separator } from '../../../ui/separator'
3
3
  import { SheetClose } from '../../../ui/sheet'
4
4
  import { cn } from '../../../lib/utils'
5
-
6
- export interface StoreCategory {
7
- label: string
8
- href: string
9
- /** Rendered as an accordion inside the mobile sheet; omit for a flat link. */
10
- children?: { label: string; href: string }[]
11
- /** Styles the link with `text-destructive` — reserve it for one entry. */
12
- emphasis?: boolean
13
- }
5
+ import type { StoreCategory } from '../../../lib/contracts'
14
6
 
15
7
  export interface UtilityLink {
16
8
  label: string
17
9
  href: string
18
10
  }
19
11
 
20
- export interface Navbar4MobileNavProps {
12
+ export interface StorefrontNavbarMobileNavProps {
21
13
  storeCategories: StoreCategory[]
22
14
  utilityLinks: UtilityLink[]
23
15
  /** When provided, an extra "Account" link is appended after the utility links. */
24
16
  accountHref?: string
25
17
  }
26
18
 
27
- export function Navbar4MobileNav({
19
+ export function StorefrontNavbarMobileNav({
28
20
  storeCategories,
29
21
  utilityLinks,
30
22
  accountHref,
31
- }: Navbar4MobileNavProps) {
23
+ }: StorefrontNavbarMobileNavProps) {
32
24
  return (
33
25
  <nav aria-label='Store' className='px-2 py-2'>
34
26
  <Accordion type='multiple'>
@@ -2,19 +2,19 @@ import { Search } from 'lucide-react'
2
2
 
3
3
  import { InputGroup, InputGroupAddon, InputGroupInput } from '../../../ui/input-group'
4
4
 
5
- export interface Navbar4SearchBarProps {
5
+ export interface StorefrontNavbarSearchBarProps {
6
6
  value: string
7
7
  onValueChange: (value: string) => void
8
8
  onDismiss?: () => void
9
9
  id?: string
10
10
  }
11
11
 
12
- export function Navbar4SearchBar({
12
+ export function StorefrontNavbarSearchBar({
13
13
  value,
14
14
  onValueChange,
15
15
  onDismiss,
16
16
  id = 'store-search',
17
- }: Navbar4SearchBarProps) {
17
+ }: StorefrontNavbarSearchBarProps) {
18
18
  return (
19
19
  <form
20
20
  role='search'
@@ -1,8 +1,9 @@
1
+ import type { ReactElement } from 'react'
1
2
  import { renderToStaticMarkup } from 'react-dom/server'
2
- import { describe, expect, it } from 'vitest'
3
- import { Navbar4 } from './navbar-4'
4
- import { Navbar4AccountMenu } from './navbar-4-account-menu'
5
- import { navbar4PropsSchema } from '../../../lib/contracts'
3
+ import { describe, expect, it, vi } from 'vitest'
4
+ import { StorefrontNavbar, StorefrontNavbarAccountRetry } from './storefront-navbar'
5
+ import { StorefrontNavbarAccountMenu } from './storefront-navbar-account-menu'
6
+ import { storefrontNavbarPropsSchema } from '../../../lib/contracts'
6
7
 
7
8
  const base = {
8
9
  storeCategories: [],
@@ -11,42 +12,49 @@ const base = {
11
12
  logoText: 'ElectroCuba',
12
13
  }
13
14
 
14
- describe('Navbar4 — account affordance', () => {
15
+ describe('StorefrontNavbar — account affordance', () => {
15
16
  const user = { name: 'Ada Lovelace', email: 'ada@example.test', initials: 'AL' }
16
17
 
17
18
  it('offers a visible account destination to anonymous shoppers', () => {
18
- const html = renderToStaticMarkup(<Navbar4 {...base} accountHref="/account" />)
19
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} accountHref="/account" />)
19
20
  expect(html).toMatch(/<a[^>]*href="\/account"[^>]*aria-label="Account"/)
20
21
  })
21
22
 
22
23
  it('renders the identity trigger without a desktop-only wrapper or empty image', () => {
23
- const html = renderToStaticMarkup(<Navbar4 {...base} user={user} />)
24
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} user={user} />)
24
25
  expect(html).toContain('Account menu for Ada Lovelace')
25
26
  expect(html).not.toMatch(/<div class="hidden lg:block"><button[^>]*aria-label="Account/)
26
27
  expect(html).not.toContain('src=""')
27
28
  })
28
29
 
30
+ it('renders the fallback initials when the identity has no avatar, never an empty image source', () => {
31
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} user={user} />)
32
+ expect(html).toContain('>AL<')
33
+ expect(html).not.toContain('src=""')
34
+ expect(html).not.toContain('<img')
35
+ })
36
+
29
37
  it('renders no account control without identity or destination', () => {
30
- const html = renderToStaticMarkup(<Navbar4 {...base} />)
38
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} />)
31
39
  expect(html).not.toContain('aria-label="Account')
32
- expect(renderToStaticMarkup(<Navbar4AccountMenu />)).toBe('')
40
+ expect(renderToStaticMarkup(<StorefrontNavbarAccountMenu />)).toBe('')
33
41
  })
34
42
 
35
43
  it('disables the identity trigger while a session operation is pending', () => {
36
- const html = renderToStaticMarkup(<Navbar4 {...base} user={user} accountPending />)
44
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} user={user} accountPending />)
37
45
  expect(html).toMatch(/<button[^>]*disabled=""[^>]*aria-label="Account menu for Ada Lovelace"/)
38
46
  })
39
47
 
40
48
  it('distinguishes unavailable identity from anonymous and offers retry', () => {
41
49
  const html = renderToStaticMarkup(
42
- <Navbar4 {...base} accountHref="/account" accountUnavailable onAccountRetry={() => {}} />,
50
+ <StorefrontNavbar {...base} accountHref="/account" accountUnavailable onAccountRetry={() => {}} />,
43
51
  )
44
52
  expect(html).toContain('aria-label="Retry account session"')
45
53
  expect(html).not.toContain('aria-label="Account"')
46
54
  })
47
55
 
48
56
  it('keeps serializable identity and feedback while stripping callbacks', () => {
49
- const parsed = navbar4PropsSchema.parse({
57
+ const parsed = storefrontNavbarPropsSchema.parse({
50
58
  ...base, user, accountHref: '/account', accountMenuItems: [{ label: 'Account', href: '/account' }],
51
59
  accountPending: true, accountUnavailable: true, onSignOut: () => {},
52
60
  })
@@ -57,16 +65,39 @@ describe('Navbar4 — account affordance', () => {
57
65
  })
58
66
  })
59
67
 
60
- describe('Navbar4 — optional logo accent', () => {
68
+ describe('StorefrontNavbar — consumer-controlled session retry', () => {
69
+ type RetryProps = { onClick?: () => void; disabled?: boolean }
70
+
71
+ it('dispatches the consumer retry action from the unavailable affordance', () => {
72
+ const onRetry = vi.fn()
73
+ const control = StorefrontNavbarAccountRetry({ onRetry }) as ReactElement<RetryProps>
74
+ expect(control.props.disabled).toBe(false)
75
+ control.props.onClick?.()
76
+ expect(onRetry).toHaveBeenCalledTimes(1)
77
+ })
78
+
79
+ it('holds retry inert while reconciliation is pending', () => {
80
+ const onRetry = vi.fn()
81
+ const control = StorefrontNavbarAccountRetry({ pending: true, onRetry }) as ReactElement<RetryProps>
82
+ expect(control.props.disabled).toBe(true)
83
+ })
84
+
85
+ it('holds retry inert when no consumer callback is bound (no empty affordance)', () => {
86
+ const control = StorefrontNavbarAccountRetry({}) as ReactElement<RetryProps>
87
+ expect(control.props.disabled).toBe(true)
88
+ })
89
+ })
90
+
91
+ describe('StorefrontNavbar — optional logo accent', () => {
61
92
  it('renders the wordmark unchanged when no accent is supplied', () => {
62
- const html = renderToStaticMarkup(<Navbar4 {...base} />)
93
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} />)
63
94
  expect(html).toContain('>ElectroCuba</span>')
64
95
  expect(html).not.toContain('text-brand-promo')
65
96
  })
66
97
 
67
98
  it('accents a matched trailing segment without duplicating the wordmark', () => {
68
99
  const html = renderToStaticMarkup(
69
- <Navbar4 {...base} logoAccent={{ text: 'Cuba', className: 'text-brand-promo' }} />,
100
+ <StorefrontNavbar {...base} logoAccent={{ text: 'Cuba', className: 'text-brand-promo' }} />,
70
101
  )
71
102
  expect(html).toContain('Electro<span class="text-brand-promo">Cuba</span>')
72
103
  expect(html.match(/Electro/g)).toHaveLength(1)
@@ -75,54 +106,54 @@ describe('Navbar4 — optional logo accent', () => {
75
106
 
76
107
  it('accents the full wordmark when the accent covers all of it', () => {
77
108
  const html = renderToStaticMarkup(
78
- <Navbar4 {...base} logoAccent={{ text: 'ElectroCuba', className: 'text-brand-promo' }} />,
109
+ <StorefrontNavbar {...base} logoAccent={{ text: 'ElectroCuba', className: 'text-brand-promo' }} />,
79
110
  )
80
111
  expect(html).toContain('<span class="text-brand-promo">ElectroCuba</span>')
81
112
  })
82
113
 
83
114
  it('does not accent when the segment is not a trailing match', () => {
84
- const html = renderToStaticMarkup(<Navbar4 {...base} logoAccent={{ text: 'Electro' }} />)
115
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} logoAccent={{ text: 'Electro' }} />)
85
116
  expect(html).toContain('>ElectroCuba</span>')
86
117
  expect(html).not.toContain('<span class="">')
87
118
  })
88
119
 
89
120
  it('keeps the wordmark as the accessible text of the logo link', () => {
90
121
  const html = renderToStaticMarkup(
91
- <Navbar4 {...base} logoHref='/' logoAccent={{ text: 'Cuba', className: 'text-brand-promo' }} />,
122
+ <StorefrontNavbar {...base} logoHref='/' logoAccent={{ text: 'Cuba', className: 'text-brand-promo' }} />,
92
123
  )
93
124
  expect(html).toContain('href="/"')
94
125
  expect(html).toContain('Electro<span class="text-brand-promo">Cuba</span>')
95
126
  })
96
127
  })
97
128
 
98
- describe('Navbar4 — optional utility strip', () => {
129
+ describe('StorefrontNavbar — optional utility strip', () => {
99
130
  // The desktop utility strip is the only element with this inner track class.
100
131
  const strip = (html: string) => html.includes('h-9 w-full max-w-7xl')
101
132
 
102
133
  it('renders no utility strip when the note is empty and there are no utility links', () => {
103
134
  const html = renderToStaticMarkup(
104
- <Navbar4 storeCategories={[]} shippingNote='' utilityLinks={[]} logoText='ElectroCuba' />,
135
+ <StorefrontNavbar storeCategories={[]} shippingNote='' utilityLinks={[]} logoText='ElectroCuba' />,
105
136
  )
106
137
  expect(strip(html)).toBe(false)
107
138
  })
108
139
 
109
140
  it('renders no utility strip when the note is omitted', () => {
110
141
  const html = renderToStaticMarkup(
111
- <Navbar4 storeCategories={[]} utilityLinks={[]} logoText='ElectroCuba' />,
142
+ <StorefrontNavbar storeCategories={[]} utilityLinks={[]} logoText='ElectroCuba' />,
112
143
  )
113
144
  expect(strip(html)).toBe(false)
114
145
  })
115
146
 
116
147
  it('treats a whitespace-only note as empty', () => {
117
148
  const html = renderToStaticMarkup(
118
- <Navbar4 storeCategories={[]} shippingNote=' ' utilityLinks={[]} logoText='ElectroCuba' />,
149
+ <StorefrontNavbar storeCategories={[]} shippingNote=' ' utilityLinks={[]} logoText='ElectroCuba' />,
119
150
  )
120
151
  expect(strip(html)).toBe(false)
121
152
  })
122
153
 
123
154
  it('renders the utility strip with a non-empty note', () => {
124
155
  const html = renderToStaticMarkup(
125
- <Navbar4 storeCategories={[]} shippingNote='Entrega segura' utilityLinks={[]} logoText='ElectroCuba' />,
156
+ <StorefrontNavbar storeCategories={[]} shippingNote='Entrega segura' utilityLinks={[]} logoText='ElectroCuba' />,
126
157
  )
127
158
  expect(strip(html)).toBe(true)
128
159
  expect(html).toContain('Entrega segura')
@@ -130,7 +161,7 @@ describe('Navbar4 — optional utility strip', () => {
130
161
 
131
162
  it('renders the utility strip when there is a utility link even with an empty note', () => {
132
163
  const html = renderToStaticMarkup(
133
- <Navbar4
164
+ <StorefrontNavbar
134
165
  storeCategories={[]}
135
166
  shippingNote=''
136
167
  utilityLinks={[{ label: 'Ayuda', href: '/help' }]}
@@ -149,9 +180,9 @@ const category = { href: '/herramientas', label: 'Herramientas' }
149
180
  const navLinkClass = (html: string) =>
150
181
  html.match(/<a href="\/herramientas" class="([^"]*hover:bg-muted[^"]*)"/)?.[1] ?? ''
151
182
 
152
- describe('Navbar4 — optional outer surface className', () => {
183
+ describe('StorefrontNavbar — optional outer surface className', () => {
153
184
  it('keeps the default surface (incl. backdrop-filter variant) and nav link color when className is omitted', () => {
154
- const html = renderToStaticMarkup(<Navbar4 {...base} storeCategories={[category]} />)
185
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} storeCategories={[category]} />)
155
186
  expect(html).toContain('bg-background/95')
156
187
  expect(html).toContain('supports-backdrop-filter:bg-background/80')
157
188
  expect(navLinkClass(html)).toContain('text-foreground')
@@ -160,7 +191,7 @@ describe('Navbar4 — optional outer surface className', () => {
160
191
 
161
192
  it('gives the consumer full surface ownership when className is supplied (no default background or variant)', () => {
162
193
  const html = renderToStaticMarkup(
163
- <Navbar4 {...base} storeCategories={[category]} className='bg-brand-nav text-brand-nav-foreground' />,
194
+ <StorefrontNavbar {...base} storeCategories={[category]} className='bg-brand-nav text-brand-nav-foreground' />,
164
195
  )
165
196
  expect(html).toContain('bg-brand-nav')
166
197
  expect(html).toContain('text-brand-nav-foreground')
@@ -174,7 +205,7 @@ describe('Navbar4 — optional outer surface className', () => {
174
205
 
175
206
  it('lets nav links inherit the branded surface text color with a correct hover foreground', () => {
176
207
  const html = renderToStaticMarkup(
177
- <Navbar4 {...base} storeCategories={[category]} className='bg-brand-nav text-brand-nav-foreground' />,
208
+ <StorefrontNavbar {...base} storeCategories={[category]} className='bg-brand-nav text-brand-nav-foreground' />,
178
209
  )
179
210
  expect(navLinkClass(html)).toContain('text-inherit')
180
211
  expect(navLinkClass(html)).toContain('hover:text-foreground')
@@ -184,15 +215,15 @@ describe('Navbar4 — optional outer surface className', () => {
184
215
  const cartTrigger = (html: string) =>
185
216
  html.match(/<button[^>]*aria-label="Cart,[^"]*"[^>]*>/)?.[0] ?? ''
186
217
 
187
- describe('Navbar4 — controlled cart sheet visibility', () => {
218
+ describe('StorefrontNavbar — controlled cart sheet visibility', () => {
188
219
  it('keeps the uncontrolled sheet closed by default (legacy consumers unaffected)', () => {
189
- const html = renderToStaticMarkup(<Navbar4 {...base} />)
220
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} />)
190
221
  expect(cartTrigger(html)).toContain('data-state="closed"')
191
222
  })
192
223
 
193
224
  it('reflects a controlled open state on the trigger', () => {
194
225
  const html = renderToStaticMarkup(
195
- <Navbar4 {...base} cartOpen={true} onCartOpenChange={() => {}} />,
226
+ <StorefrontNavbar {...base} cartOpen={true} onCartOpenChange={() => {}} />,
196
227
  )
197
228
  expect(cartTrigger(html)).toContain('data-state="open"')
198
229
  expect(cartTrigger(html)).toContain('aria-expanded="true"')
@@ -200,23 +231,23 @@ describe('Navbar4 — controlled cart sheet visibility', () => {
200
231
 
201
232
  it('reflects a controlled closed state on the trigger', () => {
202
233
  const html = renderToStaticMarkup(
203
- <Navbar4 {...base} cartOpen={false} onCartOpenChange={() => {}} />,
234
+ <StorefrontNavbar {...base} cartOpen={false} onCartOpenChange={() => {}} />,
204
235
  )
205
236
  expect(cartTrigger(html)).toContain('data-state="closed"')
206
237
  expect(cartTrigger(html)).toContain('aria-expanded="false"')
207
238
  })
208
239
  })
209
240
 
210
- describe('Navbar4 — optional showSearch (E2 task 5.4)', () => {
241
+ describe('StorefrontNavbar — optional showSearch (E2 task 5.4)', () => {
211
242
  it('renders the search control by default (legacy consumers unaffected)', () => {
212
- const html = renderToStaticMarkup(<Navbar4 {...base} />)
243
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} />)
213
244
  expect(html).toContain('role="search"')
214
245
  expect(html).toContain('type="search"')
215
246
  expect(html).toContain('aria-label="Search products"')
216
247
  })
217
248
 
218
249
  it('renders no search control when showSearch is false, keeping the rest of the chrome', () => {
219
- const html = renderToStaticMarkup(<Navbar4 {...base} showSearch={false} />)
250
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} showSearch={false} />)
220
251
  expect(html).not.toContain('role="search"')
221
252
  expect(html).not.toContain('type="search"')
222
253
  expect(html).not.toContain('aria-label="Search products"')
@@ -226,7 +257,7 @@ describe('Navbar4 — optional showSearch (E2 task 5.4)', () => {
226
257
  })
227
258
 
228
259
  it('renders the search control when showSearch is explicitly true', () => {
229
- const html = renderToStaticMarkup(<Navbar4 {...base} showSearch={true} />)
260
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} showSearch={true} />)
230
261
  expect(html).toContain('role="search"')
231
262
  expect(html).toContain('type="search"')
232
263
  })
@@ -8,12 +8,12 @@ import { Button } from '../../../ui/button'
8
8
  import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '../../../ui/sheet'
9
9
  import { cn } from '../../../lib/utils'
10
10
 
11
- import { Navbar4AccountMenu, type AccountMenuItem, type StoreUser } from './navbar-4-account-menu'
12
- import { Navbar4CartSheet, type CartItem, type CartSheetState } from './navbar-4-cart-sheet'
13
- import type { UtilityLink } from './navbar-4-mobile-nav'
14
- import { Navbar4SearchBar } from './navbar-4-search-bar'
15
- import { MegaMenu } from '../../e-commerce/navigation/mega-menu'
16
- import { MobileCategoryDrawer } from '../../e-commerce/navigation/mobile-category-drawer'
11
+ import { StorefrontNavbarAccountMenu, type AccountMenuItem, type StoreUser } from './storefront-navbar-account-menu'
12
+ import { StorefrontNavbarCartSheet, type CartItem, type CartSheetState } from './storefront-navbar-cart-sheet'
13
+ import type { UtilityLink } from './storefront-navbar-mobile-nav'
14
+ import { StorefrontNavbarSearchBar } from './storefront-navbar-search-bar'
15
+ import { MegaMenu } from './mega-menu'
16
+ import { MobileCategoryDrawer } from './mobile-category-drawer'
17
17
  import type { MenuItem } from '../../../lib/types/menu'
18
18
  import type { Money } from '@thorprovider/types'
19
19
  import type { StoreCategory } from '../../../lib/contracts'
@@ -161,7 +161,35 @@ function CartBadge({ count }: CartBadgeProps) {
161
161
  )
162
162
  }
163
163
 
164
- export interface Navbar4Props {
164
+ export interface StorefrontNavbarAccountRetryProps {
165
+ /** Consumer-owned reconciliation in flight; holds the affordance inert. */
166
+ pending?: boolean
167
+ /** Consumer-owned recovery action. Without it the affordance is inert. */
168
+ onRetry?: () => void
169
+ }
170
+
171
+ /**
172
+ * Recovery affordance for an unavailable session. The block never resolves the
173
+ * session itself: it surfaces the state and dispatches the consumer's retry.
174
+ * Extracted (hooks-free) so the dispatch is testable without a DOM.
175
+ */
176
+ export function StorefrontNavbarAccountRetry({ pending = false, onRetry }: StorefrontNavbarAccountRetryProps) {
177
+ return (
178
+ <Button
179
+ variant='ghost'
180
+ size='icon'
181
+ className='size-11'
182
+ disabled={pending || !onRetry}
183
+ aria-label='Retry account session'
184
+ title='Account session unavailable'
185
+ onClick={onRetry}
186
+ >
187
+ <RotateCcw aria-hidden='true' />
188
+ </Button>
189
+ )
190
+ }
191
+
192
+ export interface StorefrontNavbarProps {
165
193
  storeCategories: StoreCategory[]
166
194
  /**
167
195
  * Optional note shown in the desktop utility strip. When omitted (or empty)
@@ -257,7 +285,7 @@ export interface Navbar4Props {
257
285
  className?: string
258
286
  }
259
287
 
260
- export function Navbar4({
288
+ export function StorefrontNavbar({
261
289
  storeCategories,
262
290
  shippingNote = '',
263
291
  utilityLinks,
@@ -295,7 +323,7 @@ export function Navbar4({
295
323
  checkoutDisabled,
296
324
  viewCartUnavailable,
297
325
  className,
298
- }: Navbar4Props) {
326
+ }: StorefrontNavbarProps) {
299
327
  /**
300
328
  * Integration seam
301
329
  * ----------------
@@ -467,7 +495,7 @@ export function Navbar4({
467
495
  {showSearch ? (
468
496
  <>
469
497
  <div className='hidden lg:block lg:w-64'>
470
- <Navbar4SearchBar
498
+ <StorefrontNavbarSearchBar
471
499
  id='store-search'
472
500
  value={searchQuery}
473
501
  onValueChange={setSearchQuery}
@@ -489,19 +517,9 @@ export function Navbar4({
489
517
  ) : null}
490
518
 
491
519
  {accountUnavailable ? (
492
- <Button
493
- variant='ghost'
494
- size='icon'
495
- className='size-11'
496
- disabled={accountPending || !onAccountRetry}
497
- aria-label='Retry account session'
498
- title='Account session unavailable'
499
- onClick={onAccountRetry}
500
- >
501
- <RotateCcw aria-hidden='true' />
502
- </Button>
520
+ <StorefrontNavbarAccountRetry pending={accountPending} onRetry={onAccountRetry} />
503
521
  ) : user ? (
504
- <Navbar4AccountMenu
522
+ <StorefrontNavbarAccountMenu
505
523
  user={user}
506
524
  items={accountMenuItems}
507
525
  onHelp={onHelp}
@@ -542,7 +560,7 @@ export function Navbar4({
542
560
  Cart{count > 0 ? ` (${count})` : ''}
543
561
  </SheetTitle>
544
562
  </SheetHeader>
545
- <Navbar4CartSheet
563
+ <StorefrontNavbarCartSheet
546
564
  items={items}
547
565
  onQtyChange={changeQty}
548
566
  onRemove={removeItem}
@@ -571,7 +589,7 @@ export function Navbar4({
571
589
 
572
590
  {showSearch && searchOpen ? (
573
591
  <div className='border-border flex flex-col gap-0 border-t p-4 lg:hidden'>
574
- <Navbar4SearchBar
592
+ <StorefrontNavbarSearchBar
575
593
  id='store-search-mobile'
576
594
  value={searchQuery}
577
595
  onValueChange={setSearchQuery}
@@ -583,4 +601,4 @@ export function Navbar4({
583
601
  )
584
602
  }
585
603
 
586
- export default Navbar4
604
+ export default StorefrontNavbar