@shopify/create-hydrogen 4.3.13 → 5.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 (127) hide show
  1. package/dist/assets/hydrogen/bundle/analyzer.html +2045 -0
  2. package/dist/assets/hydrogen/i18n/domains.ts +28 -0
  3. package/dist/assets/hydrogen/i18n/mock-i18n-types.ts +3 -0
  4. package/dist/assets/hydrogen/i18n/subdomains.ts +27 -0
  5. package/dist/assets/hydrogen/i18n/subfolders.ts +29 -0
  6. package/dist/assets/hydrogen/routes/locale-check.ts +16 -0
  7. package/dist/assets/hydrogen/starter/.eslintignore +5 -0
  8. package/dist/assets/hydrogen/starter/.eslintrc.cjs +19 -0
  9. package/dist/assets/hydrogen/starter/.graphqlrc.yml +12 -0
  10. package/dist/assets/hydrogen/starter/CHANGELOG.md +709 -0
  11. package/dist/assets/hydrogen/starter/README.md +45 -0
  12. package/dist/assets/hydrogen/starter/app/assets/favicon.svg +28 -0
  13. package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +37 -0
  14. package/dist/assets/hydrogen/starter/app/components/Aside.tsx +76 -0
  15. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +150 -0
  16. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +68 -0
  17. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +101 -0
  18. package/dist/assets/hydrogen/starter/app/components/Footer.tsx +129 -0
  19. package/dist/assets/hydrogen/starter/app/components/Header.tsx +230 -0
  20. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +126 -0
  21. package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +80 -0
  22. package/dist/assets/hydrogen/starter/app/components/ProductImage.tsx +23 -0
  23. package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +27 -0
  24. package/dist/assets/hydrogen/starter/app/components/Search.tsx +514 -0
  25. package/dist/assets/hydrogen/starter/app/entry.client.tsx +12 -0
  26. package/dist/assets/hydrogen/starter/app/entry.server.tsx +47 -0
  27. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
  28. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +40 -0
  29. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
  30. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
  31. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
  32. package/dist/assets/hydrogen/starter/app/lib/fragments.ts +174 -0
  33. package/dist/assets/hydrogen/starter/app/lib/search.ts +29 -0
  34. package/dist/assets/hydrogen/starter/app/lib/session.ts +72 -0
  35. package/dist/assets/hydrogen/starter/app/lib/variants.ts +46 -0
  36. package/dist/assets/hydrogen/starter/app/root.tsx +191 -0
  37. package/dist/assets/hydrogen/starter/app/routes/$.tsx +11 -0
  38. package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +118 -0
  39. package/dist/assets/hydrogen/starter/app/routes/[sitemap.xml].tsx +177 -0
  40. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +182 -0
  41. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +8 -0
  42. package/dist/assets/hydrogen/starter/app/routes/account._index.tsx +5 -0
  43. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +513 -0
  44. package/dist/assets/hydrogen/starter/app/routes/account.orders.$id.tsx +195 -0
  45. package/dist/assets/hydrogen/starter/app/routes/account.orders._index.tsx +107 -0
  46. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +136 -0
  47. package/dist/assets/hydrogen/starter/app/routes/account.tsx +88 -0
  48. package/dist/assets/hydrogen/starter/app/routes/account_.authorize.tsx +5 -0
  49. package/dist/assets/hydrogen/starter/app/routes/account_.login.tsx +5 -0
  50. package/dist/assets/hydrogen/starter/app/routes/account_.logout.tsx +10 -0
  51. package/dist/assets/hydrogen/starter/app/routes/api.predictive-search.tsx +318 -0
  52. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +113 -0
  53. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +188 -0
  54. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +119 -0
  55. package/dist/assets/hydrogen/starter/app/routes/cart.$lines.tsx +69 -0
  56. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +102 -0
  57. package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +225 -0
  58. package/dist/assets/hydrogen/starter/app/routes/collections._index.tsx +146 -0
  59. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +185 -0
  60. package/dist/assets/hydrogen/starter/app/routes/discount.$code.tsx +47 -0
  61. package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +84 -0
  62. package/dist/assets/hydrogen/starter/app/routes/policies.$handle.tsx +93 -0
  63. package/dist/assets/hydrogen/starter/app/routes/policies._index.tsx +63 -0
  64. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +299 -0
  65. package/dist/assets/hydrogen/starter/app/routes/search.tsx +177 -0
  66. package/dist/assets/hydrogen/starter/app/styles/app.css +486 -0
  67. package/dist/assets/hydrogen/starter/app/styles/reset.css +129 -0
  68. package/dist/assets/hydrogen/starter/customer-accountapi.generated.d.ts +509 -0
  69. package/dist/assets/hydrogen/starter/env.d.ts +54 -0
  70. package/dist/assets/hydrogen/starter/package.json +50 -0
  71. package/dist/assets/hydrogen/starter/public/.gitkeep +0 -0
  72. package/dist/assets/hydrogen/starter/server.ts +119 -0
  73. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +1211 -0
  74. package/dist/assets/hydrogen/starter/tsconfig.json +23 -0
  75. package/dist/assets/hydrogen/starter/vite.config.ts +41 -0
  76. package/dist/assets/hydrogen/tailwind/package.json +8 -0
  77. package/dist/assets/hydrogen/tailwind/tailwind.css +6 -0
  78. package/dist/assets/hydrogen/vanilla-extract/package.json +8 -0
  79. package/dist/assets/hydrogen/virtual-routes/assets/debug-network.css +592 -0
  80. package/dist/assets/hydrogen/virtual-routes/assets/favicon-dark.svg +20 -0
  81. package/dist/assets/hydrogen/virtual-routes/assets/favicon.svg +28 -0
  82. package/dist/assets/hydrogen/virtual-routes/assets/inter-variable-font.woff2 +0 -0
  83. package/dist/assets/hydrogen/virtual-routes/assets/jetbrainsmono-variable-font.woff2 +0 -0
  84. package/dist/assets/hydrogen/virtual-routes/assets/styles.css +238 -0
  85. package/dist/assets/hydrogen/virtual-routes/components/FlameChartWrapper.jsx +123 -0
  86. package/dist/assets/hydrogen/virtual-routes/components/HydrogenLogoBaseBW.jsx +32 -0
  87. package/dist/assets/hydrogen/virtual-routes/components/HydrogenLogoBaseColor.jsx +47 -0
  88. package/dist/assets/hydrogen/virtual-routes/components/IconBanner.jsx +292 -0
  89. package/dist/assets/hydrogen/virtual-routes/components/IconClose.jsx +38 -0
  90. package/dist/assets/hydrogen/virtual-routes/components/IconDiscard.jsx +44 -0
  91. package/dist/assets/hydrogen/virtual-routes/components/IconError.jsx +61 -0
  92. package/dist/assets/hydrogen/virtual-routes/components/IconGithub.jsx +23 -0
  93. package/dist/assets/hydrogen/virtual-routes/components/IconTwitter.jsx +21 -0
  94. package/dist/assets/hydrogen/virtual-routes/components/PageLayout.jsx +7 -0
  95. package/dist/assets/hydrogen/virtual-routes/components/RequestDetails.jsx +178 -0
  96. package/dist/assets/hydrogen/virtual-routes/components/RequestTable.jsx +91 -0
  97. package/dist/assets/hydrogen/virtual-routes/components/RequestWaterfall.jsx +151 -0
  98. package/dist/assets/hydrogen/virtual-routes/lib/useDebugNetworkServer.jsx +178 -0
  99. package/dist/assets/hydrogen/virtual-routes/routes/graphiql.jsx +5 -0
  100. package/dist/assets/hydrogen/virtual-routes/routes/index.jsx +265 -0
  101. package/dist/assets/hydrogen/virtual-routes/routes/subrequest-profiler.jsx +243 -0
  102. package/dist/assets/hydrogen/virtual-routes/virtual-root.jsx +64 -0
  103. package/dist/assets/hydrogen/vite/package.json +14 -0
  104. package/dist/assets/hydrogen/vite/vite.config.js +41 -0
  105. package/dist/chokidar-2CKIHN27.js +12 -0
  106. package/dist/chunk-EO6F7WJJ.js +2 -0
  107. package/dist/chunk-FB327AH7.js +5 -0
  108. package/dist/chunk-FJPX4XUR.js +2 -0
  109. package/dist/chunk-JKOXGRAA.js +10 -0
  110. package/dist/chunk-LNQWGFTB.js +45 -0
  111. package/dist/chunk-M6JXYI3V.js +23 -0
  112. package/dist/chunk-MNT4XW23.js +2 -0
  113. package/dist/chunk-N7HFZHSO.js +1145 -0
  114. package/dist/chunk-PMDMUCNY.js +2 -0
  115. package/dist/chunk-QGLB6FFL.js +3 -0
  116. package/dist/chunk-VMIOG46Y.js +2 -0
  117. package/dist/create-app.js +1867 -34
  118. package/dist/del-CZGKV5SQ.js +11 -0
  119. package/dist/devtools-ZCRGQE64.js +8 -0
  120. package/dist/error-handler-GEQXZJ25.js +2 -0
  121. package/dist/lib-NJYCLW6W.js +22 -0
  122. package/dist/morph-ZJCCGFNC.js +30499 -0
  123. package/dist/multipart-parser-6HGDQWV7.js +3 -0
  124. package/dist/open-OD6DRFEG.js +2 -0
  125. package/dist/out-7KAQXZLP.js +2 -0
  126. package/dist/yoga.wasm +0 -0
  127. package/package.json +7 -3
@@ -0,0 +1,230 @@
1
+ import {Suspense} from 'react';
2
+ import {Await, NavLink} from '@remix-run/react';
3
+ import {type CartViewPayload, useAnalytics} from '@shopify/hydrogen';
4
+ import type {HeaderQuery, CartApiQueryFragment} from 'storefrontapi.generated';
5
+ import {useAside} from '~/components/Aside';
6
+
7
+ interface HeaderProps {
8
+ header: HeaderQuery;
9
+ cart: Promise<CartApiQueryFragment | null>;
10
+ isLoggedIn: Promise<boolean>;
11
+ publicStoreDomain: string;
12
+ }
13
+
14
+ type Viewport = 'desktop' | 'mobile';
15
+
16
+ export function Header({
17
+ header,
18
+ isLoggedIn,
19
+ cart,
20
+ publicStoreDomain,
21
+ }: HeaderProps) {
22
+ const {shop, menu} = header;
23
+ return (
24
+ <header className="header">
25
+ <NavLink prefetch="intent" to="/" style={activeLinkStyle} end>
26
+ <strong>{shop.name}</strong>
27
+ </NavLink>
28
+ <HeaderMenu
29
+ menu={menu}
30
+ viewport="desktop"
31
+ primaryDomainUrl={header.shop.primaryDomain.url}
32
+ publicStoreDomain={publicStoreDomain}
33
+ />
34
+ <HeaderCtas isLoggedIn={isLoggedIn} cart={cart} />
35
+ </header>
36
+ );
37
+ }
38
+
39
+ export function HeaderMenu({
40
+ menu,
41
+ primaryDomainUrl,
42
+ viewport,
43
+ publicStoreDomain,
44
+ }: {
45
+ menu: HeaderProps['header']['menu'];
46
+ primaryDomainUrl: HeaderProps['header']['shop']['primaryDomain']['url'];
47
+ viewport: Viewport;
48
+ publicStoreDomain: HeaderProps['publicStoreDomain'];
49
+ }) {
50
+ const className = `header-menu-${viewport}`;
51
+
52
+ function closeAside(event: React.MouseEvent<HTMLAnchorElement>) {
53
+ if (viewport === 'mobile') {
54
+ event.preventDefault();
55
+ window.location.href = event.currentTarget.href;
56
+ }
57
+ }
58
+
59
+ return (
60
+ <nav className={className} role="navigation">
61
+ {viewport === 'mobile' && (
62
+ <NavLink
63
+ end
64
+ onClick={closeAside}
65
+ prefetch="intent"
66
+ style={activeLinkStyle}
67
+ to="/"
68
+ >
69
+ Home
70
+ </NavLink>
71
+ )}
72
+ {(menu || FALLBACK_HEADER_MENU).items.map((item) => {
73
+ if (!item.url) return null;
74
+
75
+ // if the url is internal, we strip the domain
76
+ const url =
77
+ item.url.includes('myshopify.com') ||
78
+ item.url.includes(publicStoreDomain) ||
79
+ item.url.includes(primaryDomainUrl)
80
+ ? new URL(item.url).pathname
81
+ : item.url;
82
+ return (
83
+ <NavLink
84
+ className="header-menu-item"
85
+ end
86
+ key={item.id}
87
+ onClick={closeAside}
88
+ prefetch="intent"
89
+ style={activeLinkStyle}
90
+ to={url}
91
+ >
92
+ {item.title}
93
+ </NavLink>
94
+ );
95
+ })}
96
+ </nav>
97
+ );
98
+ }
99
+
100
+ function HeaderCtas({
101
+ isLoggedIn,
102
+ cart,
103
+ }: Pick<HeaderProps, 'isLoggedIn' | 'cart'>) {
104
+ return (
105
+ <nav className="header-ctas" role="navigation">
106
+ <HeaderMenuMobileToggle />
107
+ <NavLink prefetch="intent" to="/account" style={activeLinkStyle}>
108
+ <Suspense fallback="Sign in">
109
+ <Await resolve={isLoggedIn} errorElement="Sign in">
110
+ {(isLoggedIn) => (isLoggedIn ? 'Account' : 'Sign in')}
111
+ </Await>
112
+ </Suspense>
113
+ </NavLink>
114
+ <SearchToggle />
115
+ <CartToggle cart={cart} />
116
+ </nav>
117
+ );
118
+ }
119
+
120
+ function HeaderMenuMobileToggle() {
121
+ const {open} = useAside();
122
+ return (
123
+ <button
124
+ className="header-menu-mobile-toggle reset"
125
+ onClick={() => open('mobile')}
126
+ >
127
+ <h3>☰</h3>
128
+ </button>
129
+ );
130
+ }
131
+
132
+ function SearchToggle() {
133
+ const {open} = useAside();
134
+ return (
135
+ <button className="reset" onClick={() => open('search')}>
136
+ Search
137
+ </button>
138
+ );
139
+ }
140
+
141
+ function CartBadge({count}: {count: number | null}) {
142
+ const {open} = useAside();
143
+ const {publish, shop, cart, prevCart} = useAnalytics();
144
+
145
+ return (
146
+ <a
147
+ href="/cart"
148
+ onClick={(e) => {
149
+ e.preventDefault();
150
+ open('cart');
151
+ publish('cart_viewed', {
152
+ cart,
153
+ prevCart,
154
+ shop,
155
+ url: window.location.href || '',
156
+ } as CartViewPayload);
157
+ }}
158
+ >
159
+ Cart {count === null ? <span>&nbsp;</span> : count}
160
+ </a>
161
+ );
162
+ }
163
+
164
+ function CartToggle({cart}: Pick<HeaderProps, 'cart'>) {
165
+ return (
166
+ <Suspense fallback={<CartBadge count={null} />}>
167
+ <Await resolve={cart}>
168
+ {(cart) => {
169
+ if (!cart) return <CartBadge count={0} />;
170
+ return <CartBadge count={cart.totalQuantity || 0} />;
171
+ }}
172
+ </Await>
173
+ </Suspense>
174
+ );
175
+ }
176
+
177
+ const FALLBACK_HEADER_MENU = {
178
+ id: 'gid://shopify/Menu/199655587896',
179
+ items: [
180
+ {
181
+ id: 'gid://shopify/MenuItem/461609500728',
182
+ resourceId: null,
183
+ tags: [],
184
+ title: 'Collections',
185
+ type: 'HTTP',
186
+ url: '/collections',
187
+ items: [],
188
+ },
189
+ {
190
+ id: 'gid://shopify/MenuItem/461609533496',
191
+ resourceId: null,
192
+ tags: [],
193
+ title: 'Blog',
194
+ type: 'HTTP',
195
+ url: '/blogs/journal',
196
+ items: [],
197
+ },
198
+ {
199
+ id: 'gid://shopify/MenuItem/461609566264',
200
+ resourceId: null,
201
+ tags: [],
202
+ title: 'Policies',
203
+ type: 'HTTP',
204
+ url: '/policies',
205
+ items: [],
206
+ },
207
+ {
208
+ id: 'gid://shopify/MenuItem/461609599032',
209
+ resourceId: 'gid://shopify/Page/92591030328',
210
+ tags: [],
211
+ title: 'About',
212
+ type: 'PAGE',
213
+ url: '/pages/about',
214
+ items: [],
215
+ },
216
+ ],
217
+ };
218
+
219
+ function activeLinkStyle({
220
+ isActive,
221
+ isPending,
222
+ }: {
223
+ isActive: boolean;
224
+ isPending: boolean;
225
+ }) {
226
+ return {
227
+ fontWeight: isActive ? 'bold' : undefined,
228
+ color: isPending ? 'grey' : 'black',
229
+ };
230
+ }
@@ -0,0 +1,126 @@
1
+ import {Await} from '@remix-run/react';
2
+ import {Suspense} from 'react';
3
+ import type {
4
+ CartApiQueryFragment,
5
+ FooterQuery,
6
+ HeaderQuery,
7
+ } from 'storefrontapi.generated';
8
+ import {Aside} from '~/components/Aside';
9
+ import {Footer} from '~/components/Footer';
10
+ import {Header, HeaderMenu} from '~/components/Header';
11
+ import {CartMain} from '~/components/CartMain';
12
+ import {
13
+ PredictiveSearchForm,
14
+ PredictiveSearchResults,
15
+ } from '~/components/Search';
16
+
17
+ interface PageLayoutProps {
18
+ cart: Promise<CartApiQueryFragment | null>;
19
+ footer: Promise<FooterQuery | null>;
20
+ header: HeaderQuery;
21
+ isLoggedIn: Promise<boolean>;
22
+ publicStoreDomain: string;
23
+ children?: React.ReactNode;
24
+ }
25
+
26
+ export function PageLayout({
27
+ cart,
28
+ children = null,
29
+ footer,
30
+ header,
31
+ isLoggedIn,
32
+ publicStoreDomain,
33
+ }: PageLayoutProps) {
34
+ return (
35
+ <Aside.Provider>
36
+ <CartAside cart={cart} />
37
+ <SearchAside />
38
+ <MobileMenuAside header={header} publicStoreDomain={publicStoreDomain} />
39
+ {header && (
40
+ <Header
41
+ header={header}
42
+ cart={cart}
43
+ isLoggedIn={isLoggedIn}
44
+ publicStoreDomain={publicStoreDomain}
45
+ />
46
+ )}
47
+ <main>{children}</main>
48
+ <Footer
49
+ footer={footer}
50
+ header={header}
51
+ publicStoreDomain={publicStoreDomain}
52
+ />
53
+ </Aside.Provider>
54
+ );
55
+ }
56
+
57
+ function CartAside({cart}: {cart: PageLayoutProps['cart']}) {
58
+ return (
59
+ <Aside type="cart" heading="CART">
60
+ <Suspense fallback={<p>Loading cart ...</p>}>
61
+ <Await resolve={cart}>
62
+ {(cart) => {
63
+ return <CartMain cart={cart} layout="aside" />;
64
+ }}
65
+ </Await>
66
+ </Suspense>
67
+ </Aside>
68
+ );
69
+ }
70
+
71
+ function SearchAside() {
72
+ return (
73
+ <Aside type="search" heading="SEARCH">
74
+ <div className="predictive-search">
75
+ <br />
76
+ <PredictiveSearchForm>
77
+ {({fetchResults, inputRef}) => (
78
+ <div>
79
+ <input
80
+ name="q"
81
+ onChange={fetchResults}
82
+ onFocus={fetchResults}
83
+ placeholder="Search"
84
+ ref={inputRef}
85
+ type="search"
86
+ />
87
+ &nbsp;
88
+ <button
89
+ onClick={() => {
90
+ window.location.href = inputRef?.current?.value
91
+ ? `/search?q=${inputRef.current.value}`
92
+ : `/search`;
93
+ }}
94
+ >
95
+ Search
96
+ </button>
97
+ </div>
98
+ )}
99
+ </PredictiveSearchForm>
100
+ <PredictiveSearchResults />
101
+ </div>
102
+ </Aside>
103
+ );
104
+ }
105
+
106
+ function MobileMenuAside({
107
+ header,
108
+ publicStoreDomain,
109
+ }: {
110
+ header: PageLayoutProps['header'];
111
+ publicStoreDomain: PageLayoutProps['publicStoreDomain'];
112
+ }) {
113
+ return (
114
+ header.menu &&
115
+ header.shop.primaryDomain?.url && (
116
+ <Aside type="mobile" heading="MENU">
117
+ <HeaderMenu
118
+ menu={header.menu}
119
+ viewport="mobile"
120
+ primaryDomainUrl={header.shop.primaryDomain.url}
121
+ publicStoreDomain={publicStoreDomain}
122
+ />
123
+ </Aside>
124
+ )
125
+ );
126
+ }
@@ -0,0 +1,80 @@
1
+ import {Link} from '@remix-run/react';
2
+ import {type VariantOption, VariantSelector} from '@shopify/hydrogen';
3
+ import type {
4
+ ProductFragment,
5
+ ProductVariantFragment,
6
+ } from 'storefrontapi.generated';
7
+ import {AddToCartButton} from '~/components/AddToCartButton';
8
+ import {useAside} from '~/components/Aside';
9
+
10
+ export function ProductForm({
11
+ product,
12
+ selectedVariant,
13
+ variants,
14
+ }: {
15
+ product: ProductFragment;
16
+ selectedVariant: ProductFragment['selectedVariant'];
17
+ variants: Array<ProductVariantFragment>;
18
+ }) {
19
+ const {open} = useAside();
20
+ return (
21
+ <div className="product-form">
22
+ <VariantSelector
23
+ handle={product.handle}
24
+ options={product.options.filter((option) => option.values.length > 1)}
25
+ variants={variants}
26
+ >
27
+ {({option}) => <ProductOptions key={option.name} option={option} />}
28
+ </VariantSelector>
29
+ <br />
30
+ <AddToCartButton
31
+ disabled={!selectedVariant || !selectedVariant.availableForSale}
32
+ onClick={() => {
33
+ open('cart');
34
+ }}
35
+ lines={
36
+ selectedVariant
37
+ ? [
38
+ {
39
+ merchandiseId: selectedVariant.id,
40
+ quantity: 1,
41
+ selectedVariant,
42
+ },
43
+ ]
44
+ : []
45
+ }
46
+ >
47
+ {selectedVariant?.availableForSale ? 'Add to cart' : 'Sold out'}
48
+ </AddToCartButton>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ function ProductOptions({option}: {option: VariantOption}) {
54
+ return (
55
+ <div className="product-options" key={option.name}>
56
+ <h5>{option.name}</h5>
57
+ <div className="product-options-grid">
58
+ {option.values.map(({value, isAvailable, isActive, to}) => {
59
+ return (
60
+ <Link
61
+ className="product-options-item"
62
+ key={option.name + value}
63
+ prefetch="intent"
64
+ preventScrollReset
65
+ replace
66
+ to={to}
67
+ style={{
68
+ border: isActive ? '1px solid black' : '1px solid transparent',
69
+ opacity: isAvailable ? 1 : 0.3,
70
+ }}
71
+ >
72
+ {value}
73
+ </Link>
74
+ );
75
+ })}
76
+ </div>
77
+ <br />
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,23 @@
1
+ import type {ProductVariantFragment} from 'storefrontapi.generated';
2
+ import {Image} from '@shopify/hydrogen';
3
+
4
+ export function ProductImage({
5
+ image,
6
+ }: {
7
+ image: ProductVariantFragment['image'];
8
+ }) {
9
+ if (!image) {
10
+ return <div className="product-image" />;
11
+ }
12
+ return (
13
+ <div className="product-image">
14
+ <Image
15
+ alt={image.altText || 'Product Image'}
16
+ aspectRatio="1/1"
17
+ data={image}
18
+ key={image.id}
19
+ sizes="(min-width: 45em) 50vw, 100vw"
20
+ />
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,27 @@
1
+ import {Money} from '@shopify/hydrogen';
2
+ import type {MoneyV2} from '@shopify/hydrogen/storefront-api-types';
3
+
4
+ export function ProductPrice({
5
+ price,
6
+ compareAtPrice,
7
+ }: {
8
+ price?: MoneyV2;
9
+ compareAtPrice?: MoneyV2 | null;
10
+ }) {
11
+ return (
12
+ <div className="product-price">
13
+ {compareAtPrice ? (
14
+ <div className="product-price-on-sale">
15
+ {price ? <Money data={price} /> : null}
16
+ <s>
17
+ <Money data={compareAtPrice} />
18
+ </s>
19
+ </div>
20
+ ) : price ? (
21
+ <Money data={price} />
22
+ ) : (
23
+ <span>&nbsp;</span>
24
+ )}
25
+ </div>
26
+ );
27
+ }