@thorprovider/blocks 1.0.0 → 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 (91) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +156 -40
  3. package/package.json +1 -1
  4. package/src/blocks/application/login-forms/login-page.test.tsx +190 -0
  5. package/src/blocks/application/login-forms/login-page.tsx +240 -0
  6. package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +6 -8
  7. package/src/blocks/application/signup-forms/signup-page-1.tsx +127 -129
  8. package/src/blocks/application/verification/verify-1.tsx +2 -2
  9. package/src/blocks/application/viewport-ownership.test.tsx +41 -0
  10. package/src/blocks/e-commerce/category-filters/category-listing-filter-fields.tsx +218 -0
  11. package/src/blocks/e-commerce/category-filters/{category-filter-2.test.tsx → category-listing.test.tsx} +287 -199
  12. package/src/blocks/e-commerce/category-filters/category-listing.tsx +934 -0
  13. package/src/blocks/e-commerce/checkout-forms/checkout-contact-step.tsx +149 -0
  14. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-country-field.tsx → checkout-country-field.tsx} +10 -8
  15. package/src/blocks/e-commerce/checkout-forms/checkout-order-summary.tsx +230 -0
  16. package/src/blocks/e-commerce/checkout-forms/checkout-payment-step.tsx +100 -0
  17. package/src/blocks/e-commerce/checkout-forms/checkout-progress.tsx +62 -0
  18. package/src/blocks/e-commerce/checkout-forms/checkout-review-step.tsx +59 -0
  19. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-shipping-step.tsx → checkout-shipping-step.tsx} +65 -41
  20. package/src/blocks/e-commerce/checkout-forms/checkout.test.tsx +478 -0
  21. package/src/blocks/e-commerce/checkout-forms/checkout.tsx +550 -0
  22. package/src/blocks/{marketing/navbars/navbar-4-account-menu.test.tsx → e-commerce/navigation/storefront-navbar-account-menu.test.tsx} +8 -8
  23. package/src/blocks/{marketing/navbars/navbar-4-account-menu.tsx → e-commerce/navigation/storefront-navbar-account-menu.tsx} +3 -3
  24. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.test.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.test.tsx} +22 -22
  25. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.tsx} +3 -3
  26. package/src/blocks/{marketing/navbars/navbar-4-mobile-nav.tsx → e-commerce/navigation/storefront-navbar-mobile-nav.tsx} +4 -12
  27. package/src/blocks/{marketing/navbars/navbar-4-search-bar.tsx → e-commerce/navigation/storefront-navbar-search-bar.tsx} +3 -3
  28. package/src/blocks/{marketing/navbars/navbar-4.test.tsx → e-commerce/navigation/storefront-navbar.test.tsx} +40 -40
  29. package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +17 -17
  30. package/src/blocks/e-commerce/product-list/product-card.tsx +63 -40
  31. package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +2 -2
  32. package/src/blocks/marketing/footers/footer-section-accessibility.test.tsx +118 -0
  33. package/src/blocks/marketing/footers/footer-section-actions.test.tsx +131 -0
  34. package/src/blocks/marketing/footers/footer-section-data.test.tsx +127 -0
  35. package/src/blocks/marketing/footers/footer-section-native-links.test.tsx +100 -0
  36. package/src/blocks/marketing/footers/footer-section-public-api.test.tsx +11 -0
  37. package/src/blocks/marketing/footers/footer-section.test.tsx +46 -0
  38. package/src/blocks/marketing/footers/footer-section.tsx +532 -0
  39. package/src/blocks/marketing/navbars/navbar-public-api.test.tsx +57 -0
  40. package/src/blocks/marketing/navbars/navbar.mega.test.tsx +149 -0
  41. package/src/blocks/marketing/navbars/navbar.test.tsx +155 -0
  42. package/src/blocks/marketing/navbars/navbar.tsx +735 -0
  43. package/src/blocks/marketing/testimonials/testimonial-section.test.tsx +171 -0
  44. package/src/blocks/marketing/testimonials/testimonial-section.tsx +237 -0
  45. package/src/index.ts +80 -81
  46. package/src/lib/contracts-curated.test.ts +5 -5
  47. package/src/lib/contracts.test.ts +55 -0
  48. package/src/lib/contracts.ts +64 -2
  49. package/tsup.config.ts +1 -1
  50. package/src/blocks/application/login-forms/login-page-1.tsx +0 -246
  51. package/src/blocks/application/login-forms/login-page-2.tsx +0 -207
  52. package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +0 -260
  53. package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +0 -222
  54. package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +0 -921
  55. package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +0 -57
  56. package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +0 -124
  57. package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +0 -170
  58. package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +0 -52
  59. package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +0 -57
  60. package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +0 -68
  61. package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +0 -298
  62. package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +0 -47
  63. package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +0 -9
  64. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +0 -96
  65. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +0 -252
  66. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +0 -188
  67. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +0 -36
  68. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +0 -216
  69. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +0 -338
  70. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +0 -130
  71. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +0 -175
  72. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +0 -190
  73. package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +0 -312
  74. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +0 -91
  75. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +0 -110
  76. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +0 -137
  77. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +0 -68
  78. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +0 -79
  79. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +0 -172
  80. package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +0 -392
  81. package/src/blocks/marketing/footers/footer-section-1.test.tsx +0 -117
  82. package/src/blocks/marketing/footers/footer-section-1.tsx +0 -178
  83. package/src/blocks/marketing/footers/footer-section-3.tsx +0 -192
  84. package/src/blocks/marketing/footers/footer-section-4.tsx +0 -355
  85. package/src/blocks/marketing/navbars/navbar-1.tsx +0 -267
  86. package/src/blocks/marketing/navbars/navbar-2.tsx +0 -351
  87. package/src/blocks/marketing/navbars/navbar-3.tsx +0 -303
  88. package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +0 -177
  89. package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +0 -219
  90. package/src/blocks/marketing/testimonials/testimonial-1.tsx +0 -141
  91. 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,9 +1,9 @@
1
1
  import type { ReactElement } from 'react'
2
2
  import { renderToStaticMarkup } from 'react-dom/server'
3
3
  import { describe, expect, it, vi } from 'vitest'
4
- import { Navbar4, Navbar4AccountRetry } from './navbar-4'
5
- import { Navbar4AccountMenu } from './navbar-4-account-menu'
6
- import { navbar4PropsSchema } from '../../../lib/contracts'
4
+ import { StorefrontNavbar, StorefrontNavbarAccountRetry } from './storefront-navbar'
5
+ import { StorefrontNavbarAccountMenu } from './storefront-navbar-account-menu'
6
+ import { storefrontNavbarPropsSchema } from '../../../lib/contracts'
7
7
 
8
8
  const base = {
9
9
  storeCategories: [],
@@ -12,49 +12,49 @@ const base = {
12
12
  logoText: 'ElectroCuba',
13
13
  }
14
14
 
15
- describe('Navbar4 — account affordance', () => {
15
+ describe('StorefrontNavbar — account affordance', () => {
16
16
  const user = { name: 'Ada Lovelace', email: 'ada@example.test', initials: 'AL' }
17
17
 
18
18
  it('offers a visible account destination to anonymous shoppers', () => {
19
- const html = renderToStaticMarkup(<Navbar4 {...base} accountHref="/account" />)
19
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} accountHref="/account" />)
20
20
  expect(html).toMatch(/<a[^>]*href="\/account"[^>]*aria-label="Account"/)
21
21
  })
22
22
 
23
23
  it('renders the identity trigger without a desktop-only wrapper or empty image', () => {
24
- const html = renderToStaticMarkup(<Navbar4 {...base} user={user} />)
24
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} user={user} />)
25
25
  expect(html).toContain('Account menu for Ada Lovelace')
26
26
  expect(html).not.toMatch(/<div class="hidden lg:block"><button[^>]*aria-label="Account/)
27
27
  expect(html).not.toContain('src=""')
28
28
  })
29
29
 
30
30
  it('renders the fallback initials when the identity has no avatar, never an empty image source', () => {
31
- const html = renderToStaticMarkup(<Navbar4 {...base} user={user} />)
31
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} user={user} />)
32
32
  expect(html).toContain('>AL<')
33
33
  expect(html).not.toContain('src=""')
34
34
  expect(html).not.toContain('<img')
35
35
  })
36
36
 
37
37
  it('renders no account control without identity or destination', () => {
38
- const html = renderToStaticMarkup(<Navbar4 {...base} />)
38
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} />)
39
39
  expect(html).not.toContain('aria-label="Account')
40
- expect(renderToStaticMarkup(<Navbar4AccountMenu />)).toBe('')
40
+ expect(renderToStaticMarkup(<StorefrontNavbarAccountMenu />)).toBe('')
41
41
  })
42
42
 
43
43
  it('disables the identity trigger while a session operation is pending', () => {
44
- const html = renderToStaticMarkup(<Navbar4 {...base} user={user} accountPending />)
44
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} user={user} accountPending />)
45
45
  expect(html).toMatch(/<button[^>]*disabled=""[^>]*aria-label="Account menu for Ada Lovelace"/)
46
46
  })
47
47
 
48
48
  it('distinguishes unavailable identity from anonymous and offers retry', () => {
49
49
  const html = renderToStaticMarkup(
50
- <Navbar4 {...base} accountHref="/account" accountUnavailable onAccountRetry={() => {}} />,
50
+ <StorefrontNavbar {...base} accountHref="/account" accountUnavailable onAccountRetry={() => {}} />,
51
51
  )
52
52
  expect(html).toContain('aria-label="Retry account session"')
53
53
  expect(html).not.toContain('aria-label="Account"')
54
54
  })
55
55
 
56
56
  it('keeps serializable identity and feedback while stripping callbacks', () => {
57
- const parsed = navbar4PropsSchema.parse({
57
+ const parsed = storefrontNavbarPropsSchema.parse({
58
58
  ...base, user, accountHref: '/account', accountMenuItems: [{ label: 'Account', href: '/account' }],
59
59
  accountPending: true, accountUnavailable: true, onSignOut: () => {},
60
60
  })
@@ -65,12 +65,12 @@ describe('Navbar4 — account affordance', () => {
65
65
  })
66
66
  })
67
67
 
68
- describe('Navbar4 — consumer-controlled session retry', () => {
68
+ describe('StorefrontNavbar — consumer-controlled session retry', () => {
69
69
  type RetryProps = { onClick?: () => void; disabled?: boolean }
70
70
 
71
71
  it('dispatches the consumer retry action from the unavailable affordance', () => {
72
72
  const onRetry = vi.fn()
73
- const control = Navbar4AccountRetry({ onRetry }) as ReactElement<RetryProps>
73
+ const control = StorefrontNavbarAccountRetry({ onRetry }) as ReactElement<RetryProps>
74
74
  expect(control.props.disabled).toBe(false)
75
75
  control.props.onClick?.()
76
76
  expect(onRetry).toHaveBeenCalledTimes(1)
@@ -78,26 +78,26 @@ describe('Navbar4 — consumer-controlled session retry', () => {
78
78
 
79
79
  it('holds retry inert while reconciliation is pending', () => {
80
80
  const onRetry = vi.fn()
81
- const control = Navbar4AccountRetry({ pending: true, onRetry }) as ReactElement<RetryProps>
81
+ const control = StorefrontNavbarAccountRetry({ pending: true, onRetry }) as ReactElement<RetryProps>
82
82
  expect(control.props.disabled).toBe(true)
83
83
  })
84
84
 
85
85
  it('holds retry inert when no consumer callback is bound (no empty affordance)', () => {
86
- const control = Navbar4AccountRetry({}) as ReactElement<RetryProps>
86
+ const control = StorefrontNavbarAccountRetry({}) as ReactElement<RetryProps>
87
87
  expect(control.props.disabled).toBe(true)
88
88
  })
89
89
  })
90
90
 
91
- describe('Navbar4 — optional logo accent', () => {
91
+ describe('StorefrontNavbar — optional logo accent', () => {
92
92
  it('renders the wordmark unchanged when no accent is supplied', () => {
93
- const html = renderToStaticMarkup(<Navbar4 {...base} />)
93
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} />)
94
94
  expect(html).toContain('>ElectroCuba</span>')
95
95
  expect(html).not.toContain('text-brand-promo')
96
96
  })
97
97
 
98
98
  it('accents a matched trailing segment without duplicating the wordmark', () => {
99
99
  const html = renderToStaticMarkup(
100
- <Navbar4 {...base} logoAccent={{ text: 'Cuba', className: 'text-brand-promo' }} />,
100
+ <StorefrontNavbar {...base} logoAccent={{ text: 'Cuba', className: 'text-brand-promo' }} />,
101
101
  )
102
102
  expect(html).toContain('Electro<span class="text-brand-promo">Cuba</span>')
103
103
  expect(html.match(/Electro/g)).toHaveLength(1)
@@ -106,54 +106,54 @@ describe('Navbar4 — optional logo accent', () => {
106
106
 
107
107
  it('accents the full wordmark when the accent covers all of it', () => {
108
108
  const html = renderToStaticMarkup(
109
- <Navbar4 {...base} logoAccent={{ text: 'ElectroCuba', className: 'text-brand-promo' }} />,
109
+ <StorefrontNavbar {...base} logoAccent={{ text: 'ElectroCuba', className: 'text-brand-promo' }} />,
110
110
  )
111
111
  expect(html).toContain('<span class="text-brand-promo">ElectroCuba</span>')
112
112
  })
113
113
 
114
114
  it('does not accent when the segment is not a trailing match', () => {
115
- const html = renderToStaticMarkup(<Navbar4 {...base} logoAccent={{ text: 'Electro' }} />)
115
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} logoAccent={{ text: 'Electro' }} />)
116
116
  expect(html).toContain('>ElectroCuba</span>')
117
117
  expect(html).not.toContain('<span class="">')
118
118
  })
119
119
 
120
120
  it('keeps the wordmark as the accessible text of the logo link', () => {
121
121
  const html = renderToStaticMarkup(
122
- <Navbar4 {...base} logoHref='/' logoAccent={{ text: 'Cuba', className: 'text-brand-promo' }} />,
122
+ <StorefrontNavbar {...base} logoHref='/' logoAccent={{ text: 'Cuba', className: 'text-brand-promo' }} />,
123
123
  )
124
124
  expect(html).toContain('href="/"')
125
125
  expect(html).toContain('Electro<span class="text-brand-promo">Cuba</span>')
126
126
  })
127
127
  })
128
128
 
129
- describe('Navbar4 — optional utility strip', () => {
129
+ describe('StorefrontNavbar — optional utility strip', () => {
130
130
  // The desktop utility strip is the only element with this inner track class.
131
131
  const strip = (html: string) => html.includes('h-9 w-full max-w-7xl')
132
132
 
133
133
  it('renders no utility strip when the note is empty and there are no utility links', () => {
134
134
  const html = renderToStaticMarkup(
135
- <Navbar4 storeCategories={[]} shippingNote='' utilityLinks={[]} logoText='ElectroCuba' />,
135
+ <StorefrontNavbar storeCategories={[]} shippingNote='' utilityLinks={[]} logoText='ElectroCuba' />,
136
136
  )
137
137
  expect(strip(html)).toBe(false)
138
138
  })
139
139
 
140
140
  it('renders no utility strip when the note is omitted', () => {
141
141
  const html = renderToStaticMarkup(
142
- <Navbar4 storeCategories={[]} utilityLinks={[]} logoText='ElectroCuba' />,
142
+ <StorefrontNavbar storeCategories={[]} utilityLinks={[]} logoText='ElectroCuba' />,
143
143
  )
144
144
  expect(strip(html)).toBe(false)
145
145
  })
146
146
 
147
147
  it('treats a whitespace-only note as empty', () => {
148
148
  const html = renderToStaticMarkup(
149
- <Navbar4 storeCategories={[]} shippingNote=' ' utilityLinks={[]} logoText='ElectroCuba' />,
149
+ <StorefrontNavbar storeCategories={[]} shippingNote=' ' utilityLinks={[]} logoText='ElectroCuba' />,
150
150
  )
151
151
  expect(strip(html)).toBe(false)
152
152
  })
153
153
 
154
154
  it('renders the utility strip with a non-empty note', () => {
155
155
  const html = renderToStaticMarkup(
156
- <Navbar4 storeCategories={[]} shippingNote='Entrega segura' utilityLinks={[]} logoText='ElectroCuba' />,
156
+ <StorefrontNavbar storeCategories={[]} shippingNote='Entrega segura' utilityLinks={[]} logoText='ElectroCuba' />,
157
157
  )
158
158
  expect(strip(html)).toBe(true)
159
159
  expect(html).toContain('Entrega segura')
@@ -161,7 +161,7 @@ describe('Navbar4 — optional utility strip', () => {
161
161
 
162
162
  it('renders the utility strip when there is a utility link even with an empty note', () => {
163
163
  const html = renderToStaticMarkup(
164
- <Navbar4
164
+ <StorefrontNavbar
165
165
  storeCategories={[]}
166
166
  shippingNote=''
167
167
  utilityLinks={[{ label: 'Ayuda', href: '/help' }]}
@@ -180,9 +180,9 @@ const category = { href: '/herramientas', label: 'Herramientas' }
180
180
  const navLinkClass = (html: string) =>
181
181
  html.match(/<a href="\/herramientas" class="([^"]*hover:bg-muted[^"]*)"/)?.[1] ?? ''
182
182
 
183
- describe('Navbar4 — optional outer surface className', () => {
183
+ describe('StorefrontNavbar — optional outer surface className', () => {
184
184
  it('keeps the default surface (incl. backdrop-filter variant) and nav link color when className is omitted', () => {
185
- const html = renderToStaticMarkup(<Navbar4 {...base} storeCategories={[category]} />)
185
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} storeCategories={[category]} />)
186
186
  expect(html).toContain('bg-background/95')
187
187
  expect(html).toContain('supports-backdrop-filter:bg-background/80')
188
188
  expect(navLinkClass(html)).toContain('text-foreground')
@@ -191,7 +191,7 @@ describe('Navbar4 — optional outer surface className', () => {
191
191
 
192
192
  it('gives the consumer full surface ownership when className is supplied (no default background or variant)', () => {
193
193
  const html = renderToStaticMarkup(
194
- <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' />,
195
195
  )
196
196
  expect(html).toContain('bg-brand-nav')
197
197
  expect(html).toContain('text-brand-nav-foreground')
@@ -205,7 +205,7 @@ describe('Navbar4 — optional outer surface className', () => {
205
205
 
206
206
  it('lets nav links inherit the branded surface text color with a correct hover foreground', () => {
207
207
  const html = renderToStaticMarkup(
208
- <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' />,
209
209
  )
210
210
  expect(navLinkClass(html)).toContain('text-inherit')
211
211
  expect(navLinkClass(html)).toContain('hover:text-foreground')
@@ -215,15 +215,15 @@ describe('Navbar4 — optional outer surface className', () => {
215
215
  const cartTrigger = (html: string) =>
216
216
  html.match(/<button[^>]*aria-label="Cart,[^"]*"[^>]*>/)?.[0] ?? ''
217
217
 
218
- describe('Navbar4 — controlled cart sheet visibility', () => {
218
+ describe('StorefrontNavbar — controlled cart sheet visibility', () => {
219
219
  it('keeps the uncontrolled sheet closed by default (legacy consumers unaffected)', () => {
220
- const html = renderToStaticMarkup(<Navbar4 {...base} />)
220
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} />)
221
221
  expect(cartTrigger(html)).toContain('data-state="closed"')
222
222
  })
223
223
 
224
224
  it('reflects a controlled open state on the trigger', () => {
225
225
  const html = renderToStaticMarkup(
226
- <Navbar4 {...base} cartOpen={true} onCartOpenChange={() => {}} />,
226
+ <StorefrontNavbar {...base} cartOpen={true} onCartOpenChange={() => {}} />,
227
227
  )
228
228
  expect(cartTrigger(html)).toContain('data-state="open"')
229
229
  expect(cartTrigger(html)).toContain('aria-expanded="true"')
@@ -231,23 +231,23 @@ describe('Navbar4 — controlled cart sheet visibility', () => {
231
231
 
232
232
  it('reflects a controlled closed state on the trigger', () => {
233
233
  const html = renderToStaticMarkup(
234
- <Navbar4 {...base} cartOpen={false} onCartOpenChange={() => {}} />,
234
+ <StorefrontNavbar {...base} cartOpen={false} onCartOpenChange={() => {}} />,
235
235
  )
236
236
  expect(cartTrigger(html)).toContain('data-state="closed"')
237
237
  expect(cartTrigger(html)).toContain('aria-expanded="false"')
238
238
  })
239
239
  })
240
240
 
241
- describe('Navbar4 — optional showSearch (E2 task 5.4)', () => {
241
+ describe('StorefrontNavbar — optional showSearch (E2 task 5.4)', () => {
242
242
  it('renders the search control by default (legacy consumers unaffected)', () => {
243
- const html = renderToStaticMarkup(<Navbar4 {...base} />)
243
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} />)
244
244
  expect(html).toContain('role="search"')
245
245
  expect(html).toContain('type="search"')
246
246
  expect(html).toContain('aria-label="Search products"')
247
247
  })
248
248
 
249
249
  it('renders no search control when showSearch is false, keeping the rest of the chrome', () => {
250
- const html = renderToStaticMarkup(<Navbar4 {...base} showSearch={false} />)
250
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} showSearch={false} />)
251
251
  expect(html).not.toContain('role="search"')
252
252
  expect(html).not.toContain('type="search"')
253
253
  expect(html).not.toContain('aria-label="Search products"')
@@ -257,7 +257,7 @@ describe('Navbar4 — optional showSearch (E2 task 5.4)', () => {
257
257
  })
258
258
 
259
259
  it('renders the search control when showSearch is explicitly true', () => {
260
- const html = renderToStaticMarkup(<Navbar4 {...base} showSearch={true} />)
260
+ const html = renderToStaticMarkup(<StorefrontNavbar {...base} showSearch={true} />)
261
261
  expect(html).toContain('role="search"')
262
262
  expect(html).toContain('type="search"')
263
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,7 @@ function CartBadge({ count }: CartBadgeProps) {
161
161
  )
162
162
  }
163
163
 
164
- export interface Navbar4AccountRetryProps {
164
+ export interface StorefrontNavbarAccountRetryProps {
165
165
  /** Consumer-owned reconciliation in flight; holds the affordance inert. */
166
166
  pending?: boolean
167
167
  /** Consumer-owned recovery action. Without it the affordance is inert. */
@@ -173,7 +173,7 @@ export interface Navbar4AccountRetryProps {
173
173
  * session itself: it surfaces the state and dispatches the consumer's retry.
174
174
  * Extracted (hooks-free) so the dispatch is testable without a DOM.
175
175
  */
176
- export function Navbar4AccountRetry({ pending = false, onRetry }: Navbar4AccountRetryProps) {
176
+ export function StorefrontNavbarAccountRetry({ pending = false, onRetry }: StorefrontNavbarAccountRetryProps) {
177
177
  return (
178
178
  <Button
179
179
  variant='ghost'
@@ -189,7 +189,7 @@ export function Navbar4AccountRetry({ pending = false, onRetry }: Navbar4Account
189
189
  )
190
190
  }
191
191
 
192
- export interface Navbar4Props {
192
+ export interface StorefrontNavbarProps {
193
193
  storeCategories: StoreCategory[]
194
194
  /**
195
195
  * Optional note shown in the desktop utility strip. When omitted (or empty)
@@ -285,7 +285,7 @@ export interface Navbar4Props {
285
285
  className?: string
286
286
  }
287
287
 
288
- export function Navbar4({
288
+ export function StorefrontNavbar({
289
289
  storeCategories,
290
290
  shippingNote = '',
291
291
  utilityLinks,
@@ -323,7 +323,7 @@ export function Navbar4({
323
323
  checkoutDisabled,
324
324
  viewCartUnavailable,
325
325
  className,
326
- }: Navbar4Props) {
326
+ }: StorefrontNavbarProps) {
327
327
  /**
328
328
  * Integration seam
329
329
  * ----------------
@@ -495,7 +495,7 @@ export function Navbar4({
495
495
  {showSearch ? (
496
496
  <>
497
497
  <div className='hidden lg:block lg:w-64'>
498
- <Navbar4SearchBar
498
+ <StorefrontNavbarSearchBar
499
499
  id='store-search'
500
500
  value={searchQuery}
501
501
  onValueChange={setSearchQuery}
@@ -517,9 +517,9 @@ export function Navbar4({
517
517
  ) : null}
518
518
 
519
519
  {accountUnavailable ? (
520
- <Navbar4AccountRetry pending={accountPending} onRetry={onAccountRetry} />
520
+ <StorefrontNavbarAccountRetry pending={accountPending} onRetry={onAccountRetry} />
521
521
  ) : user ? (
522
- <Navbar4AccountMenu
522
+ <StorefrontNavbarAccountMenu
523
523
  user={user}
524
524
  items={accountMenuItems}
525
525
  onHelp={onHelp}
@@ -560,7 +560,7 @@ export function Navbar4({
560
560
  Cart{count > 0 ? ` (${count})` : ''}
561
561
  </SheetTitle>
562
562
  </SheetHeader>
563
- <Navbar4CartSheet
563
+ <StorefrontNavbarCartSheet
564
564
  items={items}
565
565
  onQtyChange={changeQty}
566
566
  onRemove={removeItem}
@@ -589,7 +589,7 @@ export function Navbar4({
589
589
 
590
590
  {showSearch && searchOpen ? (
591
591
  <div className='border-border flex flex-col gap-0 border-t p-4 lg:hidden'>
592
- <Navbar4SearchBar
592
+ <StorefrontNavbarSearchBar
593
593
  id='store-search-mobile'
594
594
  value={searchQuery}
595
595
  onValueChange={setSearchQuery}
@@ -601,4 +601,4 @@ export function Navbar4({
601
601
  )
602
602
  }
603
603
 
604
- export default Navbar4
604
+ export default StorefrontNavbar