create-nymbal-app 0.3.0 → 0.5.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 (167) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +14 -12
  3. package/dist/index.js.map +1 -1
  4. package/package.json +5 -4
  5. package/scaffold/project/astro/src/pages/index.astro +13 -15
  6. package/scaffold/templates/astro/_dot.node-version +1 -1
  7. package/scaffold/templates/astro/astro.config.mjs +23 -0
  8. package/scaffold/templates/astro/package.json +26 -27
  9. package/scaffold/templates/astro/src/components/SkipNav.astro +3 -1
  10. package/scaffold/templates/astro/src/elements/index.ts +11 -0
  11. package/scaffold/templates/astro/src/layouts/BaseLayout.astro +15 -4
  12. package/scaffold/templates/astro/src/lib/api.ts +126 -20
  13. package/scaffold/templates/astro/src/lib/chrome.ts +59 -0
  14. package/scaffold/templates/astro/src/lib/client.ts +4 -2
  15. package/scaffold/templates/astro/src/lib/fragments.ts +48 -0
  16. package/scaffold/templates/astro/src/lib/server-elements.ts +5 -0
  17. package/scaffold/templates/astro/src/middleware.ts +37 -0
  18. package/scaffold/templates/astro/src/pages/account/addresses.astro +5 -97
  19. package/scaffold/templates/astro/src/pages/account/login.astro +7 -34
  20. package/scaffold/templates/astro/src/pages/account/profile.astro +6 -56
  21. package/scaffold/templates/astro/src/pages/account/register.astro +7 -73
  22. package/scaffold/templates/astro/src/pages/cart.astro +14 -7
  23. package/scaffold/templates/astro/src/pages/category/[slug].astro +41 -26
  24. package/scaffold/templates/astro/src/pages/checkout.astro +34 -90
  25. package/scaffold/templates/astro/src/pages/fragments/footer.astro +5 -2
  26. package/scaffold/templates/astro/src/pages/fragments/header.astro +12 -2
  27. package/scaffold/templates/astro/src/pages/fragments/nav.astro +8 -5
  28. package/scaffold/templates/astro/src/pages/fragments/product/[slug].astro +9 -10
  29. package/scaffold/templates/astro/src/pages/index.astro +18 -15
  30. package/scaffold/templates/astro/src/pages/order/[orderNumber].astro +22 -44
  31. package/scaffold/templates/astro/src/pages/product/[slug].astro +46 -29
  32. package/scaffold/templates/astro/src/pages/products/index.astro +40 -26
  33. package/scaffold/templates/astro/src/pages/search.astro +13 -8
  34. package/scaffold/templates/astro/src/scripts/init-client.ts +14 -0
  35. package/scaffold/templates/astro/src/scripts/pdp.ts +47 -53
  36. package/scaffold/templates/astro/src/scripts/plp.ts +21 -98
  37. package/scaffold/templates/astro/src/scripts/search.ts +19 -120
  38. package/scaffold/templates/astro/src/scripts/signed-in-redirect.ts +4 -0
  39. package/scaffold/templates/astro/src/styles/global.css +49 -45
  40. package/scaffold/templates/astro/src/styles/pages/account.css +0 -90
  41. package/scaffold/templates/astro/src/styles/pages/confirmation.css +6 -0
  42. package/scaffold/templates/astro/src/styles/pages/home.css +9 -100
  43. package/scaffold/templates/astro/src/styles/pages/pdp.css +5 -167
  44. package/scaffold/templates/astro/src/styles/pages/plp.css +3 -42
  45. package/scaffold/templates/astro/src/styles/pages/search.css +13 -100
  46. package/scaffold/templates/astro/src/styles/tokens.css +56 -0
  47. package/scaffold/templates/nextjs/_dot.node-version +1 -1
  48. package/scaffold/templates/nextjs/app/(catalog)/category/[slug]/page.tsx +74 -0
  49. package/scaffold/templates/nextjs/app/(catalog)/home/[key]/page.tsx +80 -0
  50. package/scaffold/templates/nextjs/app/(catalog)/layout.tsx +13 -0
  51. package/scaffold/templates/nextjs/app/(catalog)/not-found.tsx +7 -0
  52. package/scaffold/templates/nextjs/app/(catalog)/product/[slug]/page.tsx +182 -0
  53. package/scaffold/templates/nextjs/app/(catalog)/products/page.tsx +62 -0
  54. package/scaffold/templates/nextjs/app/(catalog)/search/SearchView.tsx +90 -0
  55. package/scaffold/templates/nextjs/app/(catalog)/search/page.tsx +46 -0
  56. package/scaffold/templates/nextjs/app/(shopper)/account/addresses/AddressesView.tsx +5 -0
  57. package/scaffold/templates/nextjs/app/(shopper)/account/addresses/page.tsx +17 -0
  58. package/scaffold/templates/nextjs/app/(shopper)/account/login/LoginView.tsx +28 -0
  59. package/scaffold/templates/nextjs/app/(shopper)/account/login/page.tsx +23 -0
  60. package/scaffold/templates/nextjs/app/(shopper)/account/profile/ProfileView.tsx +5 -0
  61. package/scaffold/templates/nextjs/app/(shopper)/account/profile/page.tsx +20 -0
  62. package/scaffold/templates/nextjs/app/(shopper)/account/register/RegisterView.tsx +26 -0
  63. package/scaffold/templates/nextjs/app/(shopper)/account/register/page.tsx +11 -0
  64. package/scaffold/templates/nextjs/app/(shopper)/cart/CartView.tsx +5 -0
  65. package/scaffold/templates/nextjs/app/(shopper)/cart/page.tsx +22 -0
  66. package/scaffold/templates/nextjs/app/(shopper)/checkout/CheckoutView.tsx +27 -0
  67. package/scaffold/templates/nextjs/app/(shopper)/checkout/page.tsx +39 -0
  68. package/scaffold/templates/nextjs/app/(shopper)/layout.tsx +12 -0
  69. package/scaffold/templates/nextjs/app/{order → (shopper)/order}/[orderNumber]/page.tsx +24 -5
  70. package/scaffold/templates/nextjs/app/error.tsx +3 -7
  71. package/scaffold/templates/nextjs/app/fragments/footer/route.ts +13 -0
  72. package/scaffold/templates/nextjs/app/fragments/header/[key]/route.ts +18 -0
  73. package/scaffold/templates/nextjs/app/fragments/nav/[key]/route.ts +23 -0
  74. package/scaffold/templates/nextjs/app/fragments/product/[slug]/route.ts +22 -0
  75. package/scaffold/templates/nextjs/app/globals.css +5 -23
  76. package/scaffold/templates/nextjs/app/layout.tsx +8 -5
  77. package/scaffold/templates/nextjs/app/not-found.tsx +9 -33
  78. package/scaffold/templates/nextjs/app/tokens.css +56 -0
  79. package/scaffold/templates/nextjs/components/client/ListingControls.tsx +87 -0
  80. package/scaffold/templates/nextjs/components/client/NymbalSetup.tsx +48 -6
  81. package/scaffold/templates/nextjs/components/client/ProductInteractive.tsx +64 -28
  82. package/scaffold/templates/nextjs/components/client/elements.tsx +15 -0
  83. package/scaffold/templates/nextjs/components/server/Header.tsx +33 -97
  84. package/scaffold/templates/nextjs/components/server/NotFoundView.tsx +39 -0
  85. package/scaffold/templates/nextjs/components/server/SkipNav.tsx +7 -3
  86. package/scaffold/templates/nextjs/elements/index.ts +12 -0
  87. package/scaffold/templates/nextjs/lib/api.ts +162 -46
  88. package/scaffold/templates/nextjs/lib/chrome.ts +40 -0
  89. package/scaffold/templates/nextjs/lib/client.ts +3 -0
  90. package/scaffold/templates/nextjs/lib/fragments.ts +16 -0
  91. package/scaffold/templates/nextjs/lib/header.ts +39 -0
  92. package/scaffold/templates/nextjs/lib/render.ts +14 -0
  93. package/scaffold/templates/nextjs/next.config.mjs +32 -0
  94. package/scaffold/templates/nextjs/package.json +28 -29
  95. package/scaffold/templates/nextjs/styles/pages/account.module.css +0 -70
  96. package/scaffold/templates/nextjs/styles/pages/confirmation.module.css +6 -85
  97. package/scaffold/templates/nextjs/styles/pages/home.module.css +2 -103
  98. package/scaffold/templates/nextjs/styles/pages/pdp.module.css +3 -179
  99. package/scaffold/templates/nextjs/styles/pages/plp.module.css +8 -141
  100. package/scaffold/templates/nextjs/styles/pages/search.module.css +8 -128
  101. package/scaffold/templates/nextjs/tsconfig.dist.json +5 -0
  102. package/scaffold/templates/astro/src/components/AddressForm.astro +0 -107
  103. package/scaffold/templates/astro/src/components/Breadcrumb.astro +0 -20
  104. package/scaffold/templates/astro/src/components/CategoryNav.astro +0 -27
  105. package/scaffold/templates/astro/src/components/CheckoutSteps.astro +0 -29
  106. package/scaffold/templates/astro/src/components/Footer.astro +0 -108
  107. package/scaffold/templates/astro/src/components/Header.astro +0 -192
  108. package/scaffold/templates/astro/src/components/ImageGallery.astro +0 -42
  109. package/scaffold/templates/astro/src/components/OrderSummary.astro +0 -53
  110. package/scaffold/templates/astro/src/components/Pagination.astro +0 -13
  111. package/scaffold/templates/astro/src/components/PriceDisplay.astro +0 -25
  112. package/scaffold/templates/astro/src/components/ProductCard.astro +0 -44
  113. package/scaffold/templates/astro/src/components/RelatedProducts.astro +0 -28
  114. package/scaffold/templates/astro/src/components/ReviewForm.astro +0 -74
  115. package/scaffold/templates/astro/src/components/ReviewList.astro +0 -41
  116. package/scaffold/templates/astro/src/components/SortDropdown.astro +0 -26
  117. package/scaffold/templates/astro/src/components/StockBadge.astro +0 -32
  118. package/scaffold/templates/astro/src/scripts/auth-forms.ts +0 -82
  119. package/scaffold/templates/astro/src/scripts/cart-page.ts +0 -143
  120. package/scaffold/templates/astro/src/scripts/checkout.ts +0 -272
  121. package/scaffold/templates/astro/src/scripts/image-gallery.ts +0 -22
  122. package/scaffold/templates/astro/src/scripts/profile-form.ts +0 -57
  123. package/scaffold/templates/astro/src/scripts/review-form.ts +0 -27
  124. package/scaffold/templates/astro/src/styles/pages/cart.css +0 -243
  125. package/scaffold/templates/astro/src/styles/pages/checkout.css +0 -270
  126. package/scaffold/templates/nextjs/app/account/addresses/page.tsx +0 -10
  127. package/scaffold/templates/nextjs/app/account/login/page.tsx +0 -23
  128. package/scaffold/templates/nextjs/app/account/profile/page.tsx +0 -10
  129. package/scaffold/templates/nextjs/app/account/register/page.tsx +0 -10
  130. package/scaffold/templates/nextjs/app/cart/page.tsx +0 -10
  131. package/scaffold/templates/nextjs/app/category/[slug]/page.tsx +0 -58
  132. package/scaffold/templates/nextjs/app/checkout/page.tsx +0 -10
  133. package/scaffold/templates/nextjs/app/page.tsx +0 -63
  134. package/scaffold/templates/nextjs/app/product/[slug]/page.tsx +0 -111
  135. package/scaffold/templates/nextjs/app/products/page.tsx +0 -40
  136. package/scaffold/templates/nextjs/app/search/page.tsx +0 -24
  137. package/scaffold/templates/nextjs/components/client/AddressManager.tsx +0 -181
  138. package/scaffold/templates/nextjs/components/client/CartPage.tsx +0 -120
  139. package/scaffold/templates/nextjs/components/client/CheckoutForm.tsx +0 -523
  140. package/scaffold/templates/nextjs/components/client/ImageGallery.tsx +0 -68
  141. package/scaffold/templates/nextjs/components/client/LoginForm.tsx +0 -89
  142. package/scaffold/templates/nextjs/components/client/MiniCartWrapper.tsx +0 -12
  143. package/scaffold/templates/nextjs/components/client/ProductGrid.tsx +0 -148
  144. package/scaffold/templates/nextjs/components/client/ProfileForm.tsx +0 -112
  145. package/scaffold/templates/nextjs/components/client/RegisterForm.tsx +0 -131
  146. package/scaffold/templates/nextjs/components/client/ReviewForm.tsx +0 -116
  147. package/scaffold/templates/nextjs/components/client/SearchBarWrapper.tsx +0 -27
  148. package/scaffold/templates/nextjs/components/client/SearchResults.tsx +0 -141
  149. package/scaffold/templates/nextjs/components/client/SortDropdown.tsx +0 -35
  150. package/scaffold/templates/nextjs/components/server/Breadcrumb.tsx +0 -36
  151. package/scaffold/templates/nextjs/components/server/CategoryNav.tsx +0 -29
  152. package/scaffold/templates/nextjs/components/server/CheckoutSteps.tsx +0 -60
  153. package/scaffold/templates/nextjs/components/server/Footer.module.css +0 -64
  154. package/scaffold/templates/nextjs/components/server/Footer.tsx +0 -79
  155. package/scaffold/templates/nextjs/components/server/Header.module.css +0 -147
  156. package/scaffold/templates/nextjs/components/server/OrderSummary.tsx +0 -79
  157. package/scaffold/templates/nextjs/components/server/PriceDisplay.tsx +0 -31
  158. package/scaffold/templates/nextjs/components/server/ProductCard.tsx +0 -59
  159. package/scaffold/templates/nextjs/components/server/RelatedProducts.tsx +0 -31
  160. package/scaffold/templates/nextjs/components/server/ReviewList.tsx +0 -62
  161. package/scaffold/templates/nextjs/components/server/StockBadge.tsx +0 -19
  162. package/scaffold/templates/nextjs/styles/pages/cart.module.css +0 -240
  163. package/scaffold/templates/nextjs/styles/pages/checkout.module.css +0 -404
  164. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/layout.tsx +0 -0
  165. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/orders/[orderNumber]/page.tsx +0 -0
  166. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/orders/page.tsx +0 -0
  167. /package/scaffold/templates/nextjs/app/{account → (shopper)/account}/page.tsx +0 -0
@@ -24,38 +24,7 @@
24
24
  }
25
25
  }
26
26
 
27
- /* ---- Breadcrumbs ---- */
28
-
29
- .pdp-breadcrumbs {
30
- display: flex;
31
- align-items: center;
32
- gap: var(--nymbal-spacing-xs, 0.25rem);
33
- font-size: var(--nymbal-font-size-xs, 0.75rem);
34
- color: var(--nymbal-color-text-muted, #71717a);
35
- margin-bottom: var(--nymbal-spacing-md, 1rem);
36
- flex-wrap: wrap;
37
- }
38
-
39
- .pdp-breadcrumbs a {
40
- color: var(--nymbal-color-text-muted, #71717a);
41
- text-decoration: none;
42
- transition: color var(--nymbal-transition-fast, 150ms) ease;
43
- }
44
-
45
- .pdp-breadcrumbs a:hover {
46
- color: var(--nymbal-color-accent, #2563eb);
47
- }
48
-
49
- .pdp-breadcrumbs .separator {
50
- color: var(--nymbal-color-border, #e4e4e7);
51
- }
52
-
53
- .pdp-breadcrumbs .current {
54
- color: var(--nymbal-color-text, #18181b);
55
- font-weight: var(--nymbal-font-weight-medium, 500);
56
- }
57
-
58
- /* ---- Image gallery ---- */
27
+ /* ---- Image gallery (nymbal-image-gallery) ---- */
59
28
 
60
29
  .pdp-gallery {
61
30
  position: sticky;
@@ -63,50 +32,6 @@
63
32
  align-self: start;
64
33
  }
65
34
 
66
- .pdp-main-image {
67
- width: 100%;
68
- aspect-ratio: 1 / 1;
69
- object-fit: cover;
70
- border-radius: var(--nymbal-radius-lg, 0.75rem);
71
- background-color: #f4f4f5;
72
- margin-bottom: var(--nymbal-spacing-sm, 0.5rem);
73
- }
74
-
75
- .pdp-thumbnails {
76
- display: flex;
77
- gap: var(--nymbal-spacing-sm, 0.5rem);
78
- overflow-x: auto;
79
- scrollbar-width: thin;
80
- padding-bottom: var(--nymbal-spacing-xs, 0.25rem);
81
- }
82
-
83
- .pdp-thumbnail {
84
- width: 64px;
85
- height: 64px;
86
- object-fit: cover;
87
- border-radius: var(--nymbal-radius-md, 0.5rem);
88
- border: 2px solid transparent;
89
- cursor: pointer;
90
- flex-shrink: 0;
91
- background-color: #f4f4f5;
92
- transition: border-color var(--nymbal-transition-fast, 150ms) ease;
93
- }
94
-
95
- .pdp-thumbnail:hover {
96
- border-color: var(--nymbal-color-border, #e4e4e7);
97
- }
98
-
99
- .pdp-thumbnail.active {
100
- border-color: var(--nymbal-color-primary, #18181b);
101
- }
102
-
103
- @media (min-width: 641px) {
104
- .pdp-thumbnail {
105
- width: 80px;
106
- height: 80px;
107
- }
108
- }
109
-
110
35
  /* ---- Product info ---- */
111
36
 
112
37
  .pdp-info {
@@ -128,42 +53,6 @@
128
53
  }
129
54
  }
130
55
 
131
- .pdp-price {
132
- font-size: 1.5rem;
133
- font-weight: var(--nymbal-font-weight-bold, 700);
134
- color: var(--nymbal-color-primary, #18181b);
135
- }
136
-
137
- .pdp-price-range {
138
- font-size: var(--nymbal-font-size-sm, 0.875rem);
139
- color: var(--nymbal-color-text-muted, #71717a);
140
- font-weight: var(--nymbal-font-weight-normal, 400);
141
- margin-left: var(--nymbal-spacing-xs, 0.25rem);
142
- }
143
-
144
- .pdp-stock {
145
- display: inline-flex;
146
- align-items: center;
147
- gap: var(--nymbal-spacing-xs, 0.25rem);
148
- font-size: var(--nymbal-font-size-sm, 0.875rem);
149
- font-weight: var(--nymbal-font-weight-medium, 500);
150
- }
151
-
152
- .pdp-stock.in-stock {
153
- color: var(--nymbal-color-success, #16a34a);
154
- }
155
-
156
- .pdp-stock.out-of-stock {
157
- color: var(--nymbal-color-error, #dc2626);
158
- }
159
-
160
- .pdp-stock-dot {
161
- width: 8px;
162
- height: 8px;
163
- border-radius: var(--nymbal-radius-full, 9999px);
164
- background-color: currentColor;
165
- }
166
-
167
56
  .pdp-description {
168
57
  font-size: var(--nymbal-font-size-sm, 0.875rem);
169
58
  color: var(--nymbal-color-text-muted, #71717a);
@@ -214,59 +103,8 @@ nymbal-add-to-cart {
214
103
  margin-bottom: var(--nymbal-spacing-lg, 1.5rem);
215
104
  }
216
105
 
217
- .review-list {
218
- display: flex;
219
- flex-direction: column;
220
- gap: var(--nymbal-spacing-lg, 1.5rem);
221
- }
222
-
223
- .review-item {
224
- padding-bottom: var(--nymbal-spacing-lg, 1.5rem);
225
- border-bottom: 1px solid var(--nymbal-color-border, #e4e4e7);
226
- }
227
-
228
- .review-item:last-child {
229
- border-bottom: none;
230
- padding-bottom: 0;
231
- }
232
-
233
- .review-header {
234
- display: flex;
235
- align-items: center;
236
- justify-content: space-between;
237
- gap: var(--nymbal-spacing-sm, 0.5rem);
238
- margin-bottom: var(--nymbal-spacing-xs, 0.25rem);
239
- flex-wrap: wrap;
240
- }
241
-
242
- .review-rating {
243
- display: flex;
244
- gap: 2px;
245
- color: var(--nymbal-color-warning, #d97706);
246
- font-size: var(--nymbal-font-size-sm, 0.875rem);
247
- }
248
-
249
- .review-date {
250
- font-size: var(--nymbal-font-size-xs, 0.75rem);
251
- color: var(--nymbal-color-text-muted, #71717a);
252
- }
253
-
254
- .review-title {
255
- font-size: var(--nymbal-font-size-base, 1rem);
256
- font-weight: var(--nymbal-font-weight-medium, 500);
257
- color: var(--nymbal-color-text, #18181b);
258
- margin-bottom: var(--nymbal-spacing-xs, 0.25rem);
259
- }
260
-
261
- .review-body {
262
- font-size: var(--nymbal-font-size-sm, 0.875rem);
263
- color: var(--nymbal-color-text-muted, #71717a);
264
- line-height: 1.6;
265
- }
266
-
267
- .reviews-empty {
268
- text-align: center;
269
- padding: var(--nymbal-spacing-xl, 2rem);
270
- color: var(--nymbal-color-text-muted, #71717a);
271
- font-size: var(--nymbal-font-size-sm, 0.875rem);
106
+ .pdp-review-form {
107
+ margin-top: var(--nymbal-spacing-xl, 2rem);
108
+ padding-top: var(--nymbal-spacing-xl, 2rem);
109
+ border-top: 1px solid var(--nymbal-color-border, #e4e4e7);
272
110
  }
@@ -30,11 +30,6 @@
30
30
  margin-bottom: var(--nymbal-spacing-xs, 0.25rem);
31
31
  }
32
32
 
33
- .plp-count {
34
- font-size: var(--nymbal-font-size-sm, 0.875rem);
35
- color: var(--nymbal-color-text-muted, #71717a);
36
- }
37
-
38
33
  /* ---- Filter sidebar ---- */
39
34
 
40
35
  .plp-sidebar {
@@ -91,7 +86,7 @@
91
86
  margin-bottom: 0;
92
87
  }
93
88
 
94
- .filter-item input[type="checkbox"] {
89
+ .filter-item input[type='checkbox'] {
95
90
  width: 16px;
96
91
  height: 16px;
97
92
  accent-color: var(--nymbal-color-accent, #2563eb);
@@ -108,11 +103,11 @@
108
103
  }
109
104
 
110
105
  .filter-link:hover,
111
- .filter-link[aria-current="page"] {
106
+ .filter-link[aria-current='page'] {
112
107
  color: var(--nymbal-color-accent, #2563eb);
113
108
  }
114
109
 
115
- .filter-link[aria-current="page"] {
110
+ .filter-link[aria-current='page'] {
116
111
  font-weight: var(--nymbal-font-weight-bold, 700);
117
112
  }
118
113
 
@@ -171,40 +166,6 @@
171
166
  min-width: 0;
172
167
  }
173
168
 
174
- /* ---- Pagination ---- */
175
-
176
- .plp-pagination {
177
- display: flex;
178
- justify-content: center;
179
- gap: var(--nymbal-spacing-sm, 0.5rem);
180
- margin-top: var(--nymbal-spacing-xl, 2rem);
181
- padding-top: var(--nymbal-spacing-lg, 1.5rem);
182
- border-top: 1px solid var(--nymbal-color-border, #e4e4e7);
183
- }
184
-
185
- .plp-pagination .btn {
186
- min-width: 100px;
187
- }
188
-
189
- /* ---- Empty state ---- */
190
-
191
- .plp-empty {
192
- text-align: center;
193
- padding: 4rem var(--nymbal-spacing-md, 1rem);
194
- color: var(--nymbal-color-text-muted, #71717a);
195
- }
196
-
197
- .plp-empty-title {
198
- font-size: var(--nymbal-font-size-lg, 1.125rem);
199
- font-weight: var(--nymbal-font-weight-medium, 500);
200
- color: var(--nymbal-color-text, #18181b);
201
- margin-bottom: var(--nymbal-spacing-xs, 0.25rem);
202
- }
203
-
204
- .plp-empty-text {
205
- font-size: var(--nymbal-font-size-sm, 0.875rem);
206
- }
207
-
208
169
  /* ---- nymbal-product-filter integration ---- */
209
170
 
210
171
  nymbal-product-filter {
@@ -44,109 +44,22 @@
44
44
  box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12);
45
45
  }
46
46
 
47
- /* ---- Results area ---- */
47
+ /* ---- Category filter beside the results ---- */
48
48
 
49
- .search-results {
50
- padding-top: var(--nymbal-spacing-lg, 1.5rem);
49
+ .search-layout {
50
+ display: grid;
51
+ grid-template-columns: minmax(0, 1fr);
52
+ gap: var(--nymbal-spacing-lg, 1.5rem);
51
53
  }
52
54
 
53
- .search-results-header {
54
- display: flex;
55
- align-items: center;
56
- justify-content: space-between;
57
- margin-bottom: var(--nymbal-spacing-md, 1rem);
58
- flex-wrap: wrap;
59
- gap: var(--nymbal-spacing-sm, 0.5rem);
60
- }
61
-
62
- .search-query-label {
63
- font-size: var(--nymbal-font-size-sm, 0.875rem);
64
- color: var(--nymbal-color-text-muted, #71717a);
65
- }
66
-
67
- .search-query-label strong {
68
- color: var(--nymbal-color-text, #18181b);
69
- font-weight: var(--nymbal-font-weight-bold, 700);
70
- }
71
-
72
- .search-result-count {
73
- font-size: var(--nymbal-font-size-sm, 0.875rem);
74
- color: var(--nymbal-color-text-muted, #71717a);
75
- }
76
-
77
- /* ---- Results grid (uses .product-grid from global) ---- */
78
-
79
- /* ---- Empty / no results ---- */
80
-
81
- .search-empty {
82
- text-align: center;
83
- padding: 4rem var(--nymbal-spacing-md, 1rem);
84
- }
85
-
86
- .search-empty-icon {
87
- font-size: 3rem;
88
- margin-bottom: var(--nymbal-spacing-md, 1rem);
89
- opacity: 0.3;
90
- }
91
-
92
- .search-empty-title {
93
- font-size: var(--nymbal-font-size-lg, 1.125rem);
94
- font-weight: var(--nymbal-font-weight-medium, 500);
95
- color: var(--nymbal-color-text, #18181b);
96
- margin-bottom: var(--nymbal-spacing-xs, 0.25rem);
55
+ /* The filter carries `empty` when the search has no facets: the results take the width. The page serves it with
56
+ `empty`, so no sidebar column is drawn before the element runs. */
57
+ @media (min-width: 769px) {
58
+ .search-layout:has(nymbal-product-filter:not([empty])) {
59
+ grid-template-columns: 240px minmax(0, 1fr);
60
+ }
97
61
  }
98
62
 
99
- .search-empty-text {
100
- font-size: var(--nymbal-font-size-sm, 0.875rem);
101
- color: var(--nymbal-color-text-muted, #71717a);
102
- max-width: 28ch;
103
- margin-inline: auto;
104
- }
105
-
106
- /* ---- Suggestions ---- */
107
-
108
- .search-suggestions {
109
- margin-top: var(--nymbal-spacing-lg, 1.5rem);
110
- }
111
-
112
- .search-suggestions-title {
113
- font-size: var(--nymbal-font-size-sm, 0.875rem);
114
- font-weight: var(--nymbal-font-weight-medium, 500);
115
- color: var(--nymbal-color-text, #18181b);
116
- margin-bottom: var(--nymbal-spacing-sm, 0.5rem);
117
- }
118
-
119
- .search-suggestions-list {
120
- display: flex;
121
- flex-wrap: wrap;
122
- gap: var(--nymbal-spacing-sm, 0.5rem);
123
- }
124
-
125
- .search-suggestion {
126
- display: inline-flex;
127
- padding: var(--nymbal-spacing-xs, 0.25rem) var(--nymbal-spacing-md, 1rem);
128
- font-size: var(--nymbal-font-size-sm, 0.875rem);
129
- color: var(--nymbal-color-text, #18181b);
130
- background-color: var(--nymbal-color-surface, #fff);
131
- border: 1px solid var(--nymbal-color-border, #e4e4e7);
132
- border-radius: var(--nymbal-radius-full, 9999px);
133
- text-decoration: none;
134
- transition: border-color var(--nymbal-transition-fast, 150ms) ease,
135
- background-color var(--nymbal-transition-fast, 150ms) ease;
136
- }
137
-
138
- .search-suggestion:hover {
139
- border-color: var(--nymbal-color-accent, #2563eb);
140
- background-color: rgba(37, 99, 235, 0.04);
141
- }
142
-
143
- /* ---- Loading ---- */
144
-
145
- .search-loading {
146
- display: flex;
147
- align-items: center;
148
- justify-content: center;
149
- padding: 3rem;
150
- color: var(--nymbal-color-text-muted, #71717a);
151
- font-size: var(--nymbal-font-size-sm, 0.875rem);
63
+ .search-sidebar:has(nymbal-product-filter[empty]) {
64
+ display: none;
152
65
  }
@@ -0,0 +1,56 @@
1
+ /* ----------------------------------------------------------------
2
+ Design tokens: this store's file, edit it to restyle.
3
+ Every Nymbal element reads its look from these custom properties,
4
+ which cross into its shadow root. An element's `part`s can be
5
+ styled further with ::part(), e.g. nymbal-add-to-cart::part(button).
6
+ Defaults: @nymbalhq/web-components/tokens.css.
7
+ ---------------------------------------------------------------- */
8
+
9
+ :root {
10
+ --nymbal-color-primary: #18181b;
11
+ --nymbal-color-primary-hover: #27272a;
12
+ --nymbal-color-accent: #2563eb;
13
+ --nymbal-color-accent-hover: #1d4ed8;
14
+ --nymbal-color-success: #16a34a;
15
+ --nymbal-color-error: #dc2626;
16
+ --nymbal-color-warning: #d97706;
17
+ --nymbal-color-text: #18181b;
18
+ --nymbal-color-text-muted: #71717a;
19
+ --nymbal-color-text-inverse: #ffffff;
20
+ --nymbal-color-border: #e4e4e7;
21
+ --nymbal-color-surface: #ffffff;
22
+ --nymbal-color-overlay: rgba(0, 0, 0, 0.4);
23
+
24
+ --nymbal-font-family: system-ui, -apple-system, sans-serif;
25
+ --nymbal-font-size-xs: 0.75rem;
26
+ --nymbal-font-size-sm: 0.875rem;
27
+ --nymbal-font-size-base: 1rem;
28
+ --nymbal-font-size-lg: 1.125rem;
29
+ --nymbal-font-size-xl: 1.25rem;
30
+ --nymbal-font-weight-normal: 400;
31
+ --nymbal-font-weight-medium: 500;
32
+ --nymbal-font-weight-bold: 700;
33
+
34
+ --nymbal-spacing-xs: 0.25rem;
35
+ --nymbal-spacing-sm: 0.5rem;
36
+ --nymbal-spacing-md: 1rem;
37
+ --nymbal-spacing-lg: 1.5rem;
38
+ --nymbal-spacing-xl: 2rem;
39
+
40
+ --nymbal-radius-sm: 0.25rem;
41
+ --nymbal-radius-md: 0.375rem;
42
+ --nymbal-radius-lg: 0.5rem;
43
+ --nymbal-radius-full: 9999px;
44
+
45
+ --nymbal-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
46
+ --nymbal-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
47
+ --nymbal-shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
48
+
49
+ --nymbal-transition-fast: 150ms ease;
50
+ --nymbal-transition-normal: 200ms ease;
51
+ --nymbal-transition-slow: 300ms ease;
52
+
53
+ --nymbal-z-dropdown: 100;
54
+ --nymbal-z-drawer: 200;
55
+ --nymbal-z-toast: 300;
56
+ }
@@ -1 +1 @@
1
- 22.23.3
1
+ 26.12.1
@@ -0,0 +1,74 @@
1
+ import type { Metadata } from 'next'
2
+ import { notFound } from 'next/navigation'
3
+ import { fetchProducts, fetchCategories, fetchCapabilities, sortFromSearchParams } from '@/lib/api'
4
+ import { cardImageOf, cardProductOf, renderNymbal } from '@/lib/render'
5
+ import { Breadcrumb, ProductGrid } from '@/components/client/elements'
6
+ import { ListingControls } from '@/components/client/ListingControls'
7
+ import styles from '@/styles/pages/plp.module.css'
8
+
9
+ interface CategoryPageProps {
10
+ params: Promise<{ slug: string }>
11
+ searchParams: Promise<{ sort?: string | string[] }>
12
+ }
13
+
14
+ export async function generateMetadata({ params }: CategoryPageProps): Promise<Metadata> {
15
+ const { slug } = await params
16
+ const categories = await fetchCategories()
17
+ const category = categories.find((c) => c.slug === slug)
18
+
19
+ return {
20
+ title: category?.name ?? slug,
21
+ description: category?.description ?? `Browse products in ${slug}.`,
22
+ }
23
+ }
24
+
25
+ export default async function CategoryPage({ params, searchParams }: CategoryPageProps) {
26
+ const { slug } = await params
27
+ const capabilities = await fetchCapabilities()
28
+ const sort = sortFromSearchParams(await searchParams, capabilities?.catalog)
29
+ const [{ items, nextCursor, facets }, categories] = await Promise.all([
30
+ fetchProducts({ limit: 24, category: slug, sort }),
31
+ fetchCategories(),
32
+ ])
33
+
34
+ const category = categories.find((c) => c.slug === slug)
35
+ if (!category) notFound()
36
+ const crumbs = [{ label: 'Home', href: '/' }, { label: category.name }]
37
+ const cards = items.map(cardProductOf)
38
+ // The first card's image is the page's LCP, and the preload scanner never reaches it inside the grid's shadow root:
39
+ // a preload link, which React hoists into the head, starts it.
40
+ const firstImage = cards[0] === undefined ? null : cardImageOf(cards[0]).url
41
+ const grid = {
42
+ products: JSON.stringify(cards),
43
+ source: 'product-list',
44
+ ...(nextCursor !== null && { 'has-more': '' }),
45
+ }
46
+
47
+ return (
48
+ <div className={`container ${styles.page}`}>
49
+ {firstImage !== null && (
50
+ <link rel="preload" as="image" href={firstImage} fetchPriority="high" />
51
+ )}
52
+ <Breadcrumb
53
+ items={crumbs}
54
+ serverHtml={renderNymbal('nymbal-breadcrumb', { items: JSON.stringify(crumbs) }).__html}
55
+ />
56
+ <h1 className={styles.pageTitle}>{category.name}</h1>
57
+ {category.description && <p className="text-muted mb-lg">{category.description}</p>}
58
+ <ListingControls
59
+ sort={sort}
60
+ category={slug}
61
+ facets={facets ?? []}
62
+ sortKeys={capabilities?.catalog.sortKeys ?? []}
63
+ defaultSort={capabilities?.catalog.defaultSort}
64
+ >
65
+ <ProductGrid
66
+ products={cards}
67
+ source="product-list"
68
+ hasMore={nextCursor !== null}
69
+ serverHtml={renderNymbal('nymbal-product-grid', grid).__html}
70
+ />
71
+ </ListingControls>
72
+ </div>
73
+ )
74
+ }
@@ -0,0 +1,80 @@
1
+ import Link from 'next/link'
2
+ import { notFound } from 'next/navigation'
3
+ import { preload } from 'react-dom'
4
+ import { fetchProducts, fetchCategories } from '@/lib/api'
5
+ import { cardImageOf, cardProductOf, renderNymbal } from '@/lib/render'
6
+ import { organizationJsonLd } from '@/lib/seo'
7
+ import { HeroSection } from '@/components/server/HeroSection'
8
+ import { CategoryNav, ProductGrid } from '@/components/client/elements'
9
+ import { NewsletterSignup } from '@/components/client/NewsletterSignup'
10
+ import styles from '@/styles/pages/home.module.css'
11
+
12
+ // `/` is rewritten here (next.config.mjs): a dynamic segment with no params prerenders nothing at build, when the API
13
+ // may be down, and Next caches its first render and regenerates it in the background every 300 s.
14
+ export const revalidate = 300
15
+
16
+ export function generateStaticParams(): Array<{ key: string }> {
17
+ return []
18
+ }
19
+
20
+ interface HomePageProps {
21
+ params: Promise<{ key: string }>
22
+ }
23
+
24
+ export default async function HomePage({ params }: HomePageProps) {
25
+ if ((await params).key !== 'index') notFound()
26
+ const [{ items }, categories] = await Promise.all([
27
+ fetchProducts({ limit: 8 }),
28
+ fetchCategories(),
29
+ ])
30
+ const products = items.map(cardProductOf)
31
+ // The first card's image is the page's likely LCP, and the preload scanner never reaches it inside the grid's shadow
32
+ // root: a preload in the head starts it. React's preload(), not a <link> element: a cached render writes an element
33
+ // and its preload both.
34
+ if (products[0] !== undefined) {
35
+ preload(cardImageOf(products[0]).url, { as: 'image', fetchPriority: 'high' })
36
+ }
37
+
38
+ const jsonLd = organizationJsonLd(
39
+ 'Nymbal Store',
40
+ process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000',
41
+ )
42
+
43
+ return (
44
+ <>
45
+ <script
46
+ type="application/ld+json"
47
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
48
+ />
49
+
50
+ <HeroSection />
51
+
52
+ <section className={`container ${styles.categoriesSection}`}>
53
+ <h2 className="section-title">Shop by Category</h2>
54
+ <CategoryNav
55
+ categories={categories}
56
+ serverHtml={
57
+ renderNymbal('nymbal-category-nav', { categories: JSON.stringify(categories) }).__html
58
+ }
59
+ />
60
+ </section>
61
+
62
+ <section className={`container ${styles.featuredSection}`}>
63
+ <div className={styles.featuredHeader}>
64
+ <h2 className={styles.featuredTitle}>Featured Products</h2>
65
+ <Link href="/products" className={styles.featuredViewAll}>
66
+ View All
67
+ </Link>
68
+ </div>
69
+ <ProductGrid
70
+ products={products}
71
+ serverHtml={
72
+ renderNymbal('nymbal-product-grid', { products: JSON.stringify(products) }).__html
73
+ }
74
+ />
75
+ </section>
76
+
77
+ <NewsletterSignup />
78
+ </>
79
+ )
80
+ }
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react'
2
+ import { Header } from '@/components/server/Header'
3
+
4
+ // The catalog's pages are the same for every shopper, so Next may cache them: the header reads no request, and the
5
+ // mini-cart fills its count in the browser.
6
+ export default function CatalogLayout({ children }: { children: ReactNode }) {
7
+ return (
8
+ <>
9
+ <Header />
10
+ <main id="main">{children}</main>
11
+ </>
12
+ )
13
+ }
@@ -0,0 +1,7 @@
1
+ import { NotFoundView } from '@/components/server/NotFoundView'
2
+
3
+ // A catalog page with nothing to show (a product no longer sold), under the catalog's header. Next caches it with the
4
+ // page, so it reads no request.
5
+ export default function CatalogNotFound() {
6
+ return <NotFoundView />
7
+ }