@shopify/cli-hydrogen 3.26.0 → 4.0.0-alpha.1

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 (249) hide show
  1. package/dist/commands/hydrogen/build.js +89 -0
  2. package/dist/commands/hydrogen/dev.js +116 -0
  3. package/dist/commands/hydrogen/init.js +42 -0
  4. package/dist/commands/hydrogen/preview.js +34 -0
  5. package/dist/hooks/init.js +21 -0
  6. package/dist/templates/demo-store/.editorconfig +8 -0
  7. package/dist/templates/demo-store/.eslintignore +4 -0
  8. package/dist/templates/demo-store/.eslintrc.js +16 -0
  9. package/dist/templates/demo-store/.graphqlrc.yml +1 -0
  10. package/dist/templates/demo-store/.prettierignore +2 -0
  11. package/dist/templates/demo-store/.turbo/turbo-build.log +13 -0
  12. package/dist/templates/demo-store/app/components/AccountAddressBook.tsx +97 -0
  13. package/dist/templates/demo-store/app/components/AccountDetails.tsx +41 -0
  14. package/dist/templates/demo-store/app/components/AddToCartButton.tsx +42 -0
  15. package/dist/templates/demo-store/app/components/Breadcrumbs.tsx +36 -0
  16. package/dist/templates/demo-store/app/components/Button.tsx +56 -0
  17. package/dist/templates/demo-store/app/components/Cart.tsx +431 -0
  18. package/dist/templates/demo-store/app/components/CartLoading.tsx +50 -0
  19. package/dist/templates/demo-store/app/components/CountrySelector.tsx +180 -0
  20. package/dist/templates/demo-store/app/components/Drawer.tsx +115 -0
  21. package/dist/templates/demo-store/app/components/FeaturedCollections.tsx +54 -0
  22. package/dist/templates/demo-store/app/components/FeaturedProducts.tsx +116 -0
  23. package/dist/templates/demo-store/app/components/FeaturedSection.tsx +39 -0
  24. package/dist/templates/demo-store/app/components/GenericError.tsx +58 -0
  25. package/dist/templates/demo-store/app/components/Grid.tsx +44 -0
  26. package/dist/templates/demo-store/app/components/Hero.tsx +136 -0
  27. package/dist/templates/demo-store/app/components/Icon.tsx +253 -0
  28. package/dist/templates/demo-store/app/components/Input.tsx +24 -0
  29. package/dist/templates/demo-store/app/components/Layout.tsx +492 -0
  30. package/dist/templates/demo-store/app/components/Link.tsx +46 -0
  31. package/dist/templates/demo-store/app/components/Modal.tsx +46 -0
  32. package/dist/templates/demo-store/app/components/NotFound.tsx +22 -0
  33. package/dist/templates/demo-store/app/components/OrderCard.tsx +85 -0
  34. package/dist/templates/demo-store/app/components/Pagination.tsx +277 -0
  35. package/dist/templates/demo-store/app/components/ProductCard.tsx +146 -0
  36. package/dist/templates/demo-store/app/components/ProductGallery.tsx +114 -0
  37. package/dist/templates/demo-store/app/components/ProductGrid.tsx +93 -0
  38. package/dist/templates/demo-store/app/components/ProductSwimlane.tsx +30 -0
  39. package/dist/templates/demo-store/app/components/Skeleton.tsx +24 -0
  40. package/dist/templates/demo-store/app/components/SortFilter.tsx +411 -0
  41. package/dist/templates/demo-store/app/components/Text.tsx +192 -0
  42. package/dist/templates/demo-store/app/components/index.ts +28 -0
  43. package/dist/templates/demo-store/app/data/countries.ts +194 -0
  44. package/dist/templates/demo-store/app/data/index.ts +1037 -0
  45. package/dist/templates/demo-store/app/entry.client.tsx +4 -0
  46. package/dist/templates/demo-store/app/entry.server.tsx +26 -0
  47. package/dist/templates/demo-store/app/hooks/useCartFetchers.tsx +14 -0
  48. package/dist/templates/demo-store/app/hooks/useIsHydrated.tsx +12 -0
  49. package/dist/templates/demo-store/app/lib/const.ts +10 -0
  50. package/dist/templates/demo-store/app/lib/placeholders.ts +242 -0
  51. package/dist/templates/demo-store/app/lib/seo/common.tsx +324 -0
  52. package/dist/templates/demo-store/app/lib/seo/debugger.tsx +175 -0
  53. package/dist/templates/demo-store/app/lib/seo/image.tsx +32 -0
  54. package/dist/templates/demo-store/app/lib/seo/index.ts +4 -0
  55. package/dist/templates/demo-store/app/lib/seo/seo.tsx +24 -0
  56. package/dist/templates/demo-store/app/lib/seo/types.ts +70 -0
  57. package/dist/templates/demo-store/app/lib/session.server.ts +57 -0
  58. package/dist/templates/demo-store/app/lib/type.ts +21 -0
  59. package/dist/templates/demo-store/app/lib/utils.ts +310 -0
  60. package/dist/templates/demo-store/app/root.tsx +282 -0
  61. package/dist/templates/demo-store/app/routes/$.tsx +7 -0
  62. package/dist/templates/demo-store/app/routes/$lang/$.tsx +1 -0
  63. package/dist/templates/demo-store/app/routes/$lang/[robots.txt].tsx +1 -0
  64. package/dist/templates/demo-store/app/routes/$lang/[sitemap.xml].tsx +1 -0
  65. package/dist/templates/demo-store/app/routes/$lang/account/__private/address/$id.tsx +1 -0
  66. package/dist/templates/demo-store/app/routes/$lang/account/__private/edit.tsx +1 -0
  67. package/dist/templates/demo-store/app/routes/$lang/account/__private/logout.ts +1 -0
  68. package/dist/templates/demo-store/app/routes/$lang/account/__private/orders.$id.tsx +1 -0
  69. package/dist/templates/demo-store/app/routes/$lang/account/__public/activate.$id.$activationToken.tsx +6 -0
  70. package/dist/templates/demo-store/app/routes/$lang/account/__public/login.tsx +7 -0
  71. package/dist/templates/demo-store/app/routes/$lang/account/__public/recover.tsx +1 -0
  72. package/dist/templates/demo-store/app/routes/$lang/account/__public/register.tsx +6 -0
  73. package/dist/templates/demo-store/app/routes/$lang/account/__public/reset.$id.$resetToken.tsx +5 -0
  74. package/dist/templates/demo-store/app/routes/$lang/account.tsx +1 -0
  75. package/dist/templates/demo-store/app/routes/$lang/api/countries.tsx +1 -0
  76. package/dist/templates/demo-store/app/routes/$lang/api/products.tsx +1 -0
  77. package/dist/templates/demo-store/app/routes/$lang/cart.tsx +1 -0
  78. package/dist/templates/demo-store/app/routes/$lang/collections/$collectionHandle.tsx +6 -0
  79. package/dist/templates/demo-store/app/routes/$lang/collections/all.tsx +1 -0
  80. package/dist/templates/demo-store/app/routes/$lang/collections/index.tsx +1 -0
  81. package/dist/templates/demo-store/app/routes/$lang/featured-products.tsx +1 -0
  82. package/dist/templates/demo-store/app/routes/$lang/index.tsx +7 -0
  83. package/dist/templates/demo-store/app/routes/$lang/journal/$journalHandle.tsx +7 -0
  84. package/dist/templates/demo-store/app/routes/$lang/journal/index.tsx +1 -0
  85. package/dist/templates/demo-store/app/routes/$lang/og-image.tsx +1 -0
  86. package/dist/templates/demo-store/app/routes/$lang/pages/$pageHandle.tsx +1 -0
  87. package/dist/templates/demo-store/app/routes/$lang/policies/$policyHandle.tsx +1 -0
  88. package/dist/templates/demo-store/app/routes/$lang/policies/index.tsx +1 -0
  89. package/dist/templates/demo-store/app/routes/$lang/products/$productHandle.tsx +6 -0
  90. package/dist/templates/demo-store/app/routes/$lang/products/index.tsx +1 -0
  91. package/dist/templates/demo-store/app/routes/$lang/search.tsx +6 -0
  92. package/dist/templates/demo-store/app/routes/[robots.txt].tsx +40 -0
  93. package/dist/templates/demo-store/app/routes/[sitemap.xml].tsx +198 -0
  94. package/dist/templates/demo-store/app/routes/account/__private/address/$id.tsx +320 -0
  95. package/dist/templates/demo-store/app/routes/account/__private/edit.tsx +273 -0
  96. package/dist/templates/demo-store/app/routes/account/__private/logout.ts +29 -0
  97. package/dist/templates/demo-store/app/routes/account/__private/orders.$id.tsx +324 -0
  98. package/dist/templates/demo-store/app/routes/account/__public/activate.$id.$activationToken.tsx +218 -0
  99. package/dist/templates/demo-store/app/routes/account/__public/login.tsx +197 -0
  100. package/dist/templates/demo-store/app/routes/account/__public/recover.tsx +144 -0
  101. package/dist/templates/demo-store/app/routes/account/__public/register.tsx +184 -0
  102. package/dist/templates/demo-store/app/routes/account/__public/reset.$id.$resetToken.tsx +214 -0
  103. package/dist/templates/demo-store/app/routes/account.tsx +191 -0
  104. package/dist/templates/demo-store/app/routes/api/countries.tsx +22 -0
  105. package/dist/templates/demo-store/app/routes/api/products.tsx +116 -0
  106. package/dist/templates/demo-store/app/routes/cart.tsx +498 -0
  107. package/dist/templates/demo-store/app/routes/collections/$collectionHandle.tsx +308 -0
  108. package/dist/templates/demo-store/app/routes/collections/all.tsx +5 -0
  109. package/dist/templates/demo-store/app/routes/collections/index.tsx +195 -0
  110. package/dist/templates/demo-store/app/routes/discounts.$code.tsx +60 -0
  111. package/dist/templates/demo-store/app/routes/featured-products.tsx +58 -0
  112. package/dist/templates/demo-store/app/routes/index.tsx +254 -0
  113. package/dist/templates/demo-store/app/routes/journal/$journalHandle.tsx +147 -0
  114. package/dist/templates/demo-store/app/routes/journal/index.tsx +150 -0
  115. package/dist/templates/demo-store/app/routes/og-image.tsx +19 -0
  116. package/dist/templates/demo-store/app/routes/pages/$pageHandle.tsx +82 -0
  117. package/dist/templates/demo-store/app/routes/policies/$policyHandle.tsx +117 -0
  118. package/dist/templates/demo-store/app/routes/policies/index.tsx +104 -0
  119. package/dist/templates/demo-store/app/routes/products/$productHandle.tsx +561 -0
  120. package/dist/templates/demo-store/app/routes/products/index.tsx +155 -0
  121. package/dist/templates/demo-store/app/routes/search.tsx +205 -0
  122. package/dist/templates/demo-store/app/styles/custom-font.css +13 -0
  123. package/dist/templates/demo-store/package-lock.json +25515 -0
  124. package/dist/templates/demo-store/package.json +67 -0
  125. package/dist/templates/demo-store/playwright.config.ts +109 -0
  126. package/dist/templates/demo-store/postcss.config.js +10 -0
  127. package/dist/templates/demo-store/public/favicon.svg +28 -0
  128. package/dist/templates/demo-store/public/fonts/IBMPlexSerif-Text.woff2 +0 -0
  129. package/dist/templates/demo-store/public/fonts/IBMPlexSerif-TextItalic.woff2 +0 -0
  130. package/dist/templates/demo-store/remix.config.js +12 -0
  131. package/dist/templates/demo-store/remix.env.d.ts +34 -0
  132. package/dist/templates/demo-store/remix.init/index.ts +15 -0
  133. package/dist/templates/demo-store/remix.init/package.json +7 -0
  134. package/dist/templates/demo-store/server.ts +87 -0
  135. package/dist/templates/demo-store/styles/app.css +182 -0
  136. package/dist/templates/demo-store/tailwind.config.js +70 -0
  137. package/dist/templates/demo-store/tests/cart.test.ts +70 -0
  138. package/dist/templates/demo-store/tests/seo.test.ts +36 -0
  139. package/dist/templates/demo-store/tests/utils.ts +100 -0
  140. package/dist/templates/demo-store/tsconfig.json +26 -0
  141. package/dist/templates/hello-world/.eslintignore +4 -0
  142. package/dist/templates/hello-world/.eslintrc.js +6 -0
  143. package/dist/templates/hello-world/.graphqlrc.yml +1 -0
  144. package/dist/templates/hello-world/.turbo/turbo-build.log +9 -0
  145. package/dist/templates/hello-world/README.md +20 -0
  146. package/dist/templates/hello-world/app/components/Layout.tsx +15 -0
  147. package/dist/templates/hello-world/app/components/index.ts +1 -0
  148. package/dist/templates/hello-world/app/entry.client.tsx +4 -0
  149. package/dist/templates/hello-world/app/entry.server.tsx +21 -0
  150. package/dist/templates/hello-world/app/root.tsx +212 -0
  151. package/dist/templates/hello-world/app/routes/index.tsx +7 -0
  152. package/dist/templates/hello-world/app/styles/app.css +38 -0
  153. package/dist/templates/hello-world/package-lock.json +27641 -0
  154. package/dist/templates/hello-world/package.json +41 -0
  155. package/dist/templates/hello-world/public/favicon.svg +28 -0
  156. package/dist/templates/hello-world/remix.env.d.ts +29 -0
  157. package/dist/templates/hello-world/server.ts +127 -0
  158. package/dist/templates/hello-world/tsconfig.json +25 -0
  159. package/dist/utils/config.js +81 -0
  160. package/dist/utils/flags.js +15 -0
  161. package/dist/utils/log.js +20 -0
  162. package/dist/utils/mini-oxygen.js +70 -0
  163. package/package.json +27 -64
  164. package/tmp-create-app.mjs +29 -0
  165. package/LICENSE +0 -8
  166. package/README.md +0 -61
  167. package/dist/cli/commands/hydrogen/add/eslint.d.ts +0 -11
  168. package/dist/cli/commands/hydrogen/add/eslint.js +0 -26
  169. package/dist/cli/commands/hydrogen/add/eslint.js.map +0 -1
  170. package/dist/cli/commands/hydrogen/add/tailwind.d.ts +0 -11
  171. package/dist/cli/commands/hydrogen/add/tailwind.js +0 -26
  172. package/dist/cli/commands/hydrogen/add/tailwind.js.map +0 -1
  173. package/dist/cli/commands/hydrogen/build.d.ts +0 -14
  174. package/dist/cli/commands/hydrogen/build.js +0 -49
  175. package/dist/cli/commands/hydrogen/build.js.map +0 -1
  176. package/dist/cli/commands/hydrogen/deploy.d.ts +0 -19
  177. package/dist/cli/commands/hydrogen/deploy.js +0 -58
  178. package/dist/cli/commands/hydrogen/deploy.js.map +0 -1
  179. package/dist/cli/commands/hydrogen/dev.d.ts +0 -13
  180. package/dist/cli/commands/hydrogen/dev.js +0 -31
  181. package/dist/cli/commands/hydrogen/dev.js.map +0 -1
  182. package/dist/cli/commands/hydrogen/info.d.ts +0 -12
  183. package/dist/cli/commands/hydrogen/info.js +0 -28
  184. package/dist/cli/commands/hydrogen/info.js.map +0 -1
  185. package/dist/cli/commands/hydrogen/preview.d.ts +0 -13
  186. package/dist/cli/commands/hydrogen/preview.js +0 -46
  187. package/dist/cli/commands/hydrogen/preview.js.map +0 -1
  188. package/dist/cli/constants.d.ts +0 -15
  189. package/dist/cli/constants.js +0 -16
  190. package/dist/cli/constants.js.map +0 -1
  191. package/dist/cli/flags.d.ts +0 -4
  192. package/dist/cli/flags.js +0 -16
  193. package/dist/cli/flags.js.map +0 -1
  194. package/dist/cli/models/hydrogen.d.ts +0 -22
  195. package/dist/cli/models/hydrogen.js +0 -82
  196. package/dist/cli/models/hydrogen.js.map +0 -1
  197. package/dist/cli/prompts/git-init.d.ts +0 -1
  198. package/dist/cli/prompts/git-init.js +0 -16
  199. package/dist/cli/prompts/git-init.js.map +0 -1
  200. package/dist/cli/services/build/check-lockfile.d.ts +0 -3
  201. package/dist/cli/services/build/check-lockfile.js +0 -80
  202. package/dist/cli/services/build/check-lockfile.js.map +0 -1
  203. package/dist/cli/services/build.d.ts +0 -14
  204. package/dist/cli/services/build.js +0 -44
  205. package/dist/cli/services/build.js.map +0 -1
  206. package/dist/cli/services/deploy/config.d.ts +0 -4
  207. package/dist/cli/services/deploy/config.js +0 -49
  208. package/dist/cli/services/deploy/config.js.map +0 -1
  209. package/dist/cli/services/deploy/error.d.ts +0 -4
  210. package/dist/cli/services/deploy/error.js +0 -11
  211. package/dist/cli/services/deploy/error.js.map +0 -1
  212. package/dist/cli/services/deploy/graphql/create_deployment.d.ts +0 -10
  213. package/dist/cli/services/deploy/graphql/create_deployment.js +0 -15
  214. package/dist/cli/services/deploy/graphql/create_deployment.js.map +0 -1
  215. package/dist/cli/services/deploy/graphql/upload_deployment.d.ts +0 -1
  216. package/dist/cli/services/deploy/graphql/upload_deployment.js +0 -16
  217. package/dist/cli/services/deploy/graphql/upload_deployment.js.map +0 -1
  218. package/dist/cli/services/deploy/types.d.ts +0 -37
  219. package/dist/cli/services/deploy/types.js +0 -2
  220. package/dist/cli/services/deploy/types.js.map +0 -1
  221. package/dist/cli/services/deploy/upload.d.ts +0 -5
  222. package/dist/cli/services/deploy/upload.js +0 -81
  223. package/dist/cli/services/deploy/upload.js.map +0 -1
  224. package/dist/cli/services/deploy.d.ts +0 -2
  225. package/dist/cli/services/deploy.js +0 -103
  226. package/dist/cli/services/deploy.js.map +0 -1
  227. package/dist/cli/services/dev/check-version.d.ts +0 -1
  228. package/dist/cli/services/dev/check-version.js +0 -30
  229. package/dist/cli/services/dev/check-version.js.map +0 -1
  230. package/dist/cli/services/dev.d.ts +0 -10
  231. package/dist/cli/services/dev.js +0 -36
  232. package/dist/cli/services/dev.js.map +0 -1
  233. package/dist/cli/services/eslint.d.ts +0 -8
  234. package/dist/cli/services/eslint.js +0 -74
  235. package/dist/cli/services/eslint.js.map +0 -1
  236. package/dist/cli/services/info.d.ts +0 -7
  237. package/dist/cli/services/info.js +0 -131
  238. package/dist/cli/services/info.js.map +0 -1
  239. package/dist/cli/services/preview.d.ts +0 -12
  240. package/dist/cli/services/preview.js +0 -63
  241. package/dist/cli/services/preview.js.map +0 -1
  242. package/dist/cli/services/tailwind.d.ts +0 -9
  243. package/dist/cli/services/tailwind.js +0 -103
  244. package/dist/cli/services/tailwind.js.map +0 -1
  245. package/dist/cli/utilities/load-config.d.ts +0 -5
  246. package/dist/cli/utilities/load-config.js +0 -6
  247. package/dist/cli/utilities/load-config.js.map +0 -1
  248. package/dist/tsconfig.tsbuildinfo +0 -1
  249. package/oclif.manifest.json +0 -1
@@ -0,0 +1,492 @@
1
+ import {
2
+ type EnhancedMenu,
3
+ type EnhancedMenuItem,
4
+ useIsHomePath,
5
+ } from '~/lib/utils';
6
+ import {
7
+ Drawer,
8
+ useDrawer,
9
+ Text,
10
+ Input,
11
+ IconAccount,
12
+ IconBag,
13
+ IconSearch,
14
+ Heading,
15
+ IconMenu,
16
+ IconCaret,
17
+ Section,
18
+ CountrySelector,
19
+ Cart,
20
+ CartLoading,
21
+ Link,
22
+ } from '~/components';
23
+ import {useParams, Form, Await, useMatches} from '@remix-run/react';
24
+ import {useWindowScroll} from 'react-use';
25
+ import {Disclosure} from '@headlessui/react';
26
+ import type {LayoutData} from '~/data';
27
+ import {Suspense, useEffect, useMemo} from 'react';
28
+ import {useIsHydrated} from '~/hooks/useIsHydrated';
29
+ import {useCartFetchers} from '~/hooks/useCartFetchers';
30
+
31
+ export function Layout({
32
+ children,
33
+ layout,
34
+ }: {
35
+ children: React.ReactNode;
36
+ layout: LayoutData;
37
+ }) {
38
+ return (
39
+ <>
40
+ <div className="flex flex-col min-h-screen">
41
+ <div className="">
42
+ <a href="#mainContent" className="sr-only">
43
+ Skip to content
44
+ </a>
45
+ </div>
46
+ <Header
47
+ title={layout?.shop.name ?? 'Hydrogen'}
48
+ menu={layout?.headerMenu}
49
+ />
50
+ <main role="main" id="mainContent" className="flex-grow">
51
+ {children}
52
+ </main>
53
+ </div>
54
+ <Footer menu={layout?.footerMenu} />
55
+ </>
56
+ );
57
+ }
58
+
59
+ function Header({title, menu}: {title: string; menu?: EnhancedMenu}) {
60
+ const isHome = useIsHomePath();
61
+
62
+ const {
63
+ isOpen: isCartOpen,
64
+ openDrawer: openCart,
65
+ closeDrawer: closeCart,
66
+ } = useDrawer();
67
+
68
+ const {
69
+ isOpen: isMenuOpen,
70
+ openDrawer: openMenu,
71
+ closeDrawer: closeMenu,
72
+ } = useDrawer();
73
+
74
+ const addToCartFetchers = useCartFetchers('ADD_TO_CART');
75
+
76
+ // toggle cart drawer when adding to cart
77
+ useEffect(() => {
78
+ if (isCartOpen || !addToCartFetchers.length) return;
79
+ openCart();
80
+ }, [addToCartFetchers, isCartOpen, openCart]);
81
+
82
+ return (
83
+ <>
84
+ <CartDrawer isOpen={isCartOpen} onClose={closeCart} />
85
+ {menu && (
86
+ <MenuDrawer isOpen={isMenuOpen} onClose={closeMenu} menu={menu} />
87
+ )}
88
+ <DesktopHeader
89
+ isHome={isHome}
90
+ title={title}
91
+ menu={menu}
92
+ openCart={openCart}
93
+ />
94
+ <MobileHeader
95
+ isHome={isHome}
96
+ title={title}
97
+ openCart={openCart}
98
+ openMenu={openMenu}
99
+ />
100
+ </>
101
+ );
102
+ }
103
+
104
+ function CartDrawer({isOpen, onClose}: {isOpen: boolean; onClose: () => void}) {
105
+ const [root] = useMatches();
106
+
107
+ return (
108
+ <Drawer open={isOpen} onClose={onClose} heading="Cart" openFrom="right">
109
+ <div className="grid">
110
+ <Suspense fallback={<CartLoading />}>
111
+ <Await resolve={root.data?.cart}>
112
+ {(cart) => <Cart layout="drawer" onClose={onClose} cart={cart} />}
113
+ </Await>
114
+ </Suspense>
115
+ </div>
116
+ </Drawer>
117
+ );
118
+ }
119
+
120
+ export function MenuDrawer({
121
+ isOpen,
122
+ onClose,
123
+ menu,
124
+ }: {
125
+ isOpen: boolean;
126
+ onClose: () => void;
127
+ menu: EnhancedMenu;
128
+ }) {
129
+ return (
130
+ <Drawer open={isOpen} onClose={onClose} openFrom="left" heading="Menu">
131
+ <div className="grid">
132
+ <MenuMobileNav menu={menu} onClose={onClose} />
133
+ </div>
134
+ </Drawer>
135
+ );
136
+ }
137
+
138
+ function MenuMobileNav({
139
+ menu,
140
+ onClose,
141
+ }: {
142
+ menu: EnhancedMenu;
143
+ onClose: () => void;
144
+ }) {
145
+ const styles = {
146
+ link: 'pb-1',
147
+ linkActive: 'pb-1 border-b -mb-px',
148
+ };
149
+
150
+ return (
151
+ <nav className="grid gap-4 p-6 sm:gap-6 sm:px-12 sm:py-8">
152
+ {/* Top level menu items */}
153
+ {(menu?.items || []).map((item) => (
154
+ <span key={item.id} className="block">
155
+ <Link
156
+ to={item.to}
157
+ target={item.target}
158
+ onClick={onClose}
159
+ className={({isActive}) =>
160
+ isActive ? styles.linkActive : styles.link
161
+ }
162
+ >
163
+ <Text as="span" size="copy">
164
+ {item.title}
165
+ </Text>
166
+ </Link>
167
+ </span>
168
+ ))}
169
+ </nav>
170
+ );
171
+ }
172
+
173
+ function MobileHeader({
174
+ title,
175
+ isHome,
176
+ openCart,
177
+ openMenu,
178
+ }: {
179
+ title: string;
180
+ isHome: boolean;
181
+ openCart: () => void;
182
+ openMenu: () => void;
183
+ }) {
184
+ const {y} = useWindowScroll();
185
+
186
+ const styles = {
187
+ button: 'relative flex items-center justify-center w-8 h-8',
188
+ container: `${
189
+ isHome
190
+ ? 'bg-primary/80 dark:bg-contrast/60 text-contrast dark:text-primary shadow-darkHeader'
191
+ : 'bg-contrast/80 text-primary'
192
+ } ${
193
+ y > 50 && !isHome ? 'shadow-lightHeader ' : ''
194
+ }flex lg:hidden items-center h-nav sticky backdrop-blur-lg z-40 top-0 justify-between w-full leading-none gap-4 px-4 md:px-8`,
195
+ };
196
+ const params = useParams();
197
+
198
+ return (
199
+ <header role="banner" className={styles.container}>
200
+ <div className="flex items-center justify-start w-full gap-4">
201
+ <button onClick={openMenu} className={styles.button}>
202
+ <IconMenu />
203
+ </button>
204
+ <Form
205
+ method="get"
206
+ action={params.lang ? `/${params.lang}/search` : '/search'}
207
+ className="items-center gap-2 sm:flex"
208
+ >
209
+ <button type="submit" className={styles.button}>
210
+ <IconSearch />
211
+ </button>
212
+ <Input
213
+ className={
214
+ isHome
215
+ ? 'focus:border-contrast/20 dark:focus:border-primary/20'
216
+ : 'focus:border-primary/20'
217
+ }
218
+ type="search"
219
+ variant="minisearch"
220
+ placeholder="Search"
221
+ name="q"
222
+ />
223
+ </Form>
224
+ </div>
225
+
226
+ <Link
227
+ className="flex items-center self-stretch leading-[3rem] md:leading-[4rem] justify-center flex-grow w-full h-full"
228
+ to="/"
229
+ >
230
+ <Heading className="font-bold text-center" as={isHome ? 'h1' : 'h2'}>
231
+ {title}
232
+ </Heading>
233
+ </Link>
234
+
235
+ <div className="flex items-center justify-end w-full gap-4">
236
+ <Link to="/account" className={styles.button}>
237
+ <IconAccount />
238
+ </Link>
239
+ <CartCount isHome={isHome} openCart={openCart} />
240
+ </div>
241
+ </header>
242
+ );
243
+ }
244
+
245
+ function DesktopHeader({
246
+ isHome,
247
+ menu,
248
+ openCart,
249
+ title,
250
+ }: {
251
+ isHome: boolean;
252
+ openCart: () => void;
253
+ menu?: EnhancedMenu;
254
+ title: string;
255
+ }) {
256
+ const {y} = useWindowScroll();
257
+ const params = useParams();
258
+
259
+ const styles = {
260
+ link: 'pb-1',
261
+ linkActive: 'pb-1 border-b -mb-px',
262
+ button:
263
+ 'relative flex items-center justify-center w-8 h-8 focus:ring-primary/5',
264
+ container: `${
265
+ isHome
266
+ ? 'bg-primary/80 dark:bg-contrast/60 text-contrast dark:text-primary shadow-darkHeader'
267
+ : 'bg-contrast/80 text-primary'
268
+ } ${
269
+ y > 50 && !isHome ? 'shadow-lightHeader ' : ''
270
+ }hidden h-nav lg:flex items-center sticky transition duration-300 backdrop-blur-lg z-40 top-0 justify-between w-full leading-none gap-8 px-12 py-8`,
271
+ };
272
+
273
+ return (
274
+ <header role="banner" className={styles.container}>
275
+ <div className="flex gap-12">
276
+ <Link className="font-bold" to="/" prefetch="intent">
277
+ {title}
278
+ </Link>
279
+ <nav className="flex gap-8">
280
+ {/* Top level menu items */}
281
+ {(menu?.items || []).map((item) => (
282
+ <Link
283
+ key={item.id}
284
+ to={item.to}
285
+ target={item.target}
286
+ prefetch="intent"
287
+ className={({isActive}) =>
288
+ isActive ? styles.linkActive : styles.link
289
+ }
290
+ >
291
+ {item.title}
292
+ </Link>
293
+ ))}
294
+ </nav>
295
+ </div>
296
+ <div className="flex items-center gap-1">
297
+ <Form
298
+ method="get"
299
+ action={params.lang ? `/${params.lang}/search` : '/search'}
300
+ className="flex items-center gap-2"
301
+ >
302
+ <Input
303
+ className={
304
+ isHome
305
+ ? 'focus:border-contrast/20 dark:focus:border-primary/20'
306
+ : 'focus:border-primary/20'
307
+ }
308
+ type="search"
309
+ variant="minisearch"
310
+ placeholder="Search"
311
+ name="q"
312
+ />
313
+ <button type="submit" className={styles.button}>
314
+ <IconSearch />
315
+ </button>
316
+ </Form>
317
+ <Link to="/account" className={styles.button}>
318
+ <IconAccount />
319
+ </Link>
320
+ <CartCount isHome={isHome} openCart={openCart} />
321
+ </div>
322
+ </header>
323
+ );
324
+ }
325
+
326
+ function CartCount({
327
+ isHome,
328
+ openCart,
329
+ }: {
330
+ isHome: boolean;
331
+ openCart: () => void;
332
+ }) {
333
+ const [root] = useMatches();
334
+
335
+ return (
336
+ <Suspense fallback={<Badge count={0} dark={isHome} openCart={openCart} />}>
337
+ <Await resolve={root.data?.cart}>
338
+ {(cart) => (
339
+ <Badge
340
+ dark={isHome}
341
+ openCart={openCart}
342
+ count={cart?.totalQuantity || 0}
343
+ />
344
+ )}
345
+ </Await>
346
+ </Suspense>
347
+ );
348
+ }
349
+
350
+ function Badge({
351
+ openCart,
352
+ dark,
353
+ count,
354
+ }: {
355
+ count: number;
356
+ dark: boolean;
357
+ openCart: () => void;
358
+ }) {
359
+ const isHydrated = useIsHydrated();
360
+
361
+ const BadgeCounter = useMemo(
362
+ () => (
363
+ <>
364
+ <IconBag />
365
+ <div
366
+ className={`${
367
+ dark
368
+ ? 'text-primary bg-contrast dark:text-contrast dark:bg-primary'
369
+ : 'text-contrast bg-primary'
370
+ } absolute bottom-1 right-1 text-[0.625rem] font-medium subpixel-antialiased h-3 min-w-[0.75rem] flex items-center justify-center leading-none text-center rounded-full w-auto px-[0.125rem] pb-px`}
371
+ >
372
+ <span>{count || 0}</span>
373
+ </div>
374
+ </>
375
+ ),
376
+ [count, dark],
377
+ );
378
+
379
+ return isHydrated ? (
380
+ <button
381
+ onClick={openCart}
382
+ className={
383
+ 'relative flex items-center justify-center w-8 h-8 focus:ring-primary/5'
384
+ }
385
+ >
386
+ {BadgeCounter}
387
+ </button>
388
+ ) : (
389
+ <Link
390
+ to="/cart"
391
+ className={
392
+ 'relative flex items-center justify-center w-8 h-8 focus:ring-primary/5'
393
+ }
394
+ >
395
+ {BadgeCounter}
396
+ </Link>
397
+ );
398
+ }
399
+
400
+ function Footer({menu}: {menu?: EnhancedMenu}) {
401
+ const isHome = useIsHomePath();
402
+ const itemsCount = menu
403
+ ? menu?.items?.length + 1 > 4
404
+ ? 4
405
+ : menu?.items?.length + 1
406
+ : [];
407
+
408
+ return (
409
+ <Section
410
+ divider={isHome ? 'none' : 'top'}
411
+ as="footer"
412
+ role="contentinfo"
413
+ className={`grid min-h-[25rem] items-start grid-flow-row w-full gap-6 py-8 px-6 md:px-8 lg:px-12
414
+ border-b md:gap-8 lg:gap-12 grid-cols-1 md:grid-cols-2 lg:grid-cols-${itemsCount}
415
+ bg-primary dark:bg-contrast dark:text-primary text-contrast overflow-hidden`}
416
+ >
417
+ <FooterMenu menu={menu} />
418
+ <CountrySelector />
419
+ <div
420
+ className={`self-end pt-8 opacity-50 md:col-span-2 lg:col-span-${itemsCount}`}
421
+ >
422
+ &copy; {new Date().getFullYear()} / Shopify, Inc. Hydrogen is an MIT
423
+ Licensed Open Source project. This website is carbon&nbsp;neutral.
424
+ </div>
425
+ </Section>
426
+ );
427
+ }
428
+
429
+ const FooterLink = ({item}: {item: EnhancedMenuItem}) => {
430
+ if (item.to.startsWith('http')) {
431
+ return (
432
+ <a href={item.to} target={item.target} rel="noopener noreferrer">
433
+ {item.title}
434
+ </a>
435
+ );
436
+ }
437
+
438
+ return (
439
+ <Link to={item.to} target={item.target} prefetch="intent">
440
+ {item.title}
441
+ </Link>
442
+ );
443
+ };
444
+
445
+ function FooterMenu({menu}: {menu?: EnhancedMenu}) {
446
+ const styles = {
447
+ section: 'grid gap-4',
448
+ nav: 'grid gap-2 pb-6',
449
+ };
450
+
451
+ return (
452
+ <>
453
+ {(menu?.items || []).map((item: EnhancedMenuItem) => (
454
+ <section key={item.id} className={styles.section}>
455
+ <Disclosure>
456
+ {({open}) => (
457
+ <>
458
+ <Disclosure.Button className="text-left md:cursor-default">
459
+ <Heading className="flex justify-between" size="lead" as="h3">
460
+ {item.title}
461
+ {item?.items?.length > 0 && (
462
+ <span className="md:hidden">
463
+ <IconCaret direction={open ? 'up' : 'down'} />
464
+ </span>
465
+ )}
466
+ </Heading>
467
+ </Disclosure.Button>
468
+ {item?.items?.length > 0 ? (
469
+ <div
470
+ className={`${
471
+ open ? `max-h-48 h-fit` : `max-h-0 md:max-h-fit`
472
+ } overflow-hidden transition-all duration-300`}
473
+ >
474
+ <Suspense data-comment="This suspense fixes a hydration bug in Disclosure.Panel with static prop">
475
+ <Disclosure.Panel static>
476
+ <nav className={styles.nav}>
477
+ {item.items.map((subItem) => (
478
+ <FooterLink key={subItem.id} item={subItem} />
479
+ ))}
480
+ </nav>
481
+ </Disclosure.Panel>
482
+ </Suspense>
483
+ </div>
484
+ ) : null}
485
+ </>
486
+ )}
487
+ </Disclosure>
488
+ </section>
489
+ ))}{' '}
490
+ </>
491
+ );
492
+ }
@@ -0,0 +1,46 @@
1
+ import {
2
+ Link as RemixLink,
3
+ NavLink as RemixNavLink,
4
+ type NavLinkProps as RemixNavLinkProps,
5
+ type LinkProps as RemixLinkProps,
6
+ useMatches,
7
+ } from '@remix-run/react';
8
+
9
+ type LinkProps = Omit<RemixLinkProps, 'className'> & {
10
+ className?: RemixNavLinkProps['className'] | RemixLinkProps['className'];
11
+ };
12
+
13
+ /**
14
+ * In our app, we've chosen to wrap Remix's `Link` component to add
15
+ * helper functionality. If the `to` value is a string (not object syntax),
16
+ * we prefix the locale to the path if there is one.
17
+ *
18
+ * You could implement the same behavior throughout your app using the
19
+ * Remix-native nested routes. However, your route and component structure
20
+ * changes the level of nesting required to get the locale into the route,
21
+ * which may not be ideal for shared components or layouts.
22
+ *
23
+ * Likewise, your internationalization strategy may not require a locale
24
+ * in the pathname and instead rely on a domain, cookie, or header.
25
+ *
26
+ * Ultimately, it is up to you to decide how to implement this behavior.
27
+ */
28
+ export function Link(props: LinkProps) {
29
+ const {to, className, ...resOfProps} = props;
30
+ const [root] = useMatches();
31
+ const selectedLocale = root.data?.selectedLocale;
32
+
33
+ let toWithLocale = to;
34
+
35
+ if (typeof to === 'string') {
36
+ toWithLocale = selectedLocale ? `${selectedLocale.pathPrefix}${to}` : to;
37
+ }
38
+
39
+ if (typeof className === 'function') {
40
+ return (
41
+ <RemixNavLink to={toWithLocale} className={className} {...resOfProps} />
42
+ );
43
+ }
44
+
45
+ return <RemixLink to={toWithLocale} className={className} {...resOfProps} />;
46
+ }
@@ -0,0 +1,46 @@
1
+ import {IconClose, Link} from '~/components';
2
+
3
+ export function Modal({
4
+ children,
5
+ cancelLink,
6
+ }: {
7
+ children: React.ReactNode;
8
+ cancelLink: string;
9
+ }) {
10
+ return (
11
+ <div
12
+ className="relative z-50"
13
+ aria-labelledby="modal-title"
14
+ role="dialog"
15
+ aria-modal="true"
16
+ id="modal-bg"
17
+ >
18
+ <div className="fixed inset-0 transition-opacity bg-opacity-75 bg-primary/40"></div>
19
+ <div className="fixed inset-0 z-50 overflow-y-auto">
20
+ <div className="flex items-center justify-center min-h-full p-4 text-center sm:p-0">
21
+ <div
22
+ className="relative flex-1 px-4 pt-5 pb-4 overflow-hidden text-left transition-all transform rounded shadow-xl bg-contrast sm:my-12 sm:flex-none sm:w-full sm:max-w-sm sm:p-6"
23
+ role="button"
24
+ onClick={(e) => {
25
+ e.stopPropagation();
26
+ }}
27
+ onKeyPress={(e) => {
28
+ e.stopPropagation();
29
+ }}
30
+ tabIndex={0}
31
+ >
32
+ <div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
33
+ <Link
34
+ to={cancelLink}
35
+ className="p-4 -m-4 transition text-primary hover:text-primary/50"
36
+ >
37
+ <IconClose aria-label="Close panel" />
38
+ </Link>
39
+ </div>
40
+ {children}
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ );
46
+ }
@@ -0,0 +1,22 @@
1
+ import {Button} from './Button';
2
+ import {FeaturedSection} from './FeaturedSection';
3
+ import {PageHeader, Text} from './Text';
4
+
5
+ export function NotFound({type = 'page'}: {type?: string}) {
6
+ const heading = `We’ve lost this ${type}`;
7
+ const description = `We couldn’t find the ${type} you’re looking for. Try checking the URL or heading back to the home page.`;
8
+
9
+ return (
10
+ <>
11
+ <PageHeader heading={heading}>
12
+ <Text width="narrow" as="p">
13
+ {description}
14
+ </Text>
15
+ <Button width="auto" variant="secondary" to={'/'}>
16
+ Take me to the home page
17
+ </Button>
18
+ </PageHeader>
19
+ <FeaturedSection />
20
+ </>
21
+ );
22
+ }
@@ -0,0 +1,85 @@
1
+ import {flattenConnection} from '@shopify/hydrogen-react';
2
+ import type {
3
+ Order,
4
+ OrderLineItem,
5
+ } from '@shopify/hydrogen-react/storefront-api-types';
6
+ import {Heading, Text, Link} from '~/components';
7
+ import {statusMessage} from '~/lib/utils';
8
+
9
+ export function OrderCard({order}: {order: Order}) {
10
+ if (!order?.id) return null;
11
+ const [legacyOrderId, key] = order!.id!.split('/').pop()!.split('?');
12
+ const lineItems = flattenConnection<OrderLineItem>(order?.lineItems);
13
+
14
+ return (
15
+ <li className="grid text-center border rounded">
16
+ <Link
17
+ className="grid items-center gap-4 p-4 md:gap-6 md:p-6 md:grid-cols-2"
18
+ to={`/account/orders/${legacyOrderId}?${key}`}
19
+ prefetch="intent"
20
+ >
21
+ {lineItems[0].variant?.image && (
22
+ <div className="card-image aspect-square bg-primary/5">
23
+ <img
24
+ width={168}
25
+ height={168}
26
+ className="w-full fadeIn cover"
27
+ alt={lineItems[0].variant?.image?.altText ?? 'Order image'}
28
+ src={lineItems[0].variant?.image.url}
29
+ />
30
+ </div>
31
+ )}
32
+ <div
33
+ className={`flex-col justify-center text-left ${
34
+ !lineItems[0].variant?.image && 'md:col-span-2'
35
+ }`}
36
+ >
37
+ <Heading as="h3" format size="copy">
38
+ {lineItems.length > 1
39
+ ? `${lineItems[0].title} +${lineItems.length - 1} more`
40
+ : lineItems[0].title}
41
+ </Heading>
42
+ <dl className="grid grid-gap-1">
43
+ <dt className="sr-only">Order ID</dt>
44
+ <dd>
45
+ <Text size="fine" color="subtle">
46
+ Order No. {order.orderNumber}
47
+ </Text>
48
+ </dd>
49
+ <dt className="sr-only">Order Date</dt>
50
+ <dd>
51
+ <Text size="fine" color="subtle">
52
+ {new Date(order.processedAt).toDateString()}
53
+ </Text>
54
+ </dd>
55
+ <dt className="sr-only">Fulfillment Status</dt>
56
+ <dd className="mt-2">
57
+ <span
58
+ className={`px-3 py-1 text-xs font-medium rounded-full ${
59
+ order.fulfillmentStatus === 'FULFILLED'
60
+ ? 'bg-green-100 text-green-800'
61
+ : 'bg-primary/5 text-primary/50'
62
+ }`}
63
+ >
64
+ <Text size="fine">
65
+ {statusMessage(order.fulfillmentStatus)}
66
+ </Text>
67
+ </span>
68
+ </dd>
69
+ </dl>
70
+ </div>
71
+ </Link>
72
+ <div className="self-end border-t">
73
+ <Link
74
+ className="block w-full p-2 text-center"
75
+ to={`/account/orders/${legacyOrderId}?${key}`}
76
+ prefetch="intent"
77
+ >
78
+ <Text color="subtle" className="ml-3">
79
+ View Details
80
+ </Text>
81
+ </Link>
82
+ </div>
83
+ </li>
84
+ );
85
+ }