├── .editorconfig ├── .github ├── renovate.json └── workflows │ └── main.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── commitlint.config.js ├── examples ├── embedded-studio │ ├── .env.example │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .graphqlrc.yml │ ├── .npmrc │ ├── CHANGELOG.md │ ├── README.md │ ├── app │ │ ├── assets │ │ │ └── favicon.svg │ │ ├── components │ │ │ ├── Aside.tsx │ │ │ ├── Cart.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Header.tsx │ │ │ ├── Layout.tsx │ │ │ └── Search.tsx │ │ ├── entry.client.tsx │ │ ├── entry.server.tsx │ │ ├── graphql │ │ │ └── customer-account │ │ │ │ ├── CustomerAddressMutations.ts │ │ │ │ ├── CustomerDetailsQuery.ts │ │ │ │ ├── CustomerOrderQuery.ts │ │ │ │ ├── CustomerOrdersQuery.ts │ │ │ │ └── CustomerUpdateMutation.ts │ │ ├── lib │ │ │ ├── fragments.ts │ │ │ ├── root-data.ts │ │ │ ├── search.ts │ │ │ ├── session.ts │ │ │ └── variants.ts │ │ ├── root.tsx │ │ ├── routes │ │ │ ├── $.tsx │ │ │ ├── [robots.txt].tsx │ │ │ ├── [sitemap.xml].tsx │ │ │ ├── _index.tsx │ │ │ ├── account.$.tsx │ │ │ ├── account._index.tsx │ │ │ ├── account.addresses.tsx │ │ │ ├── account.orders.$id.tsx │ │ │ ├── account.orders._index.tsx │ │ │ ├── account.profile.tsx │ │ │ ├── account.tsx │ │ │ ├── account_.authorize.tsx │ │ │ ├── account_.login.tsx │ │ │ ├── account_.logout.tsx │ │ │ ├── api.predictive-search.tsx │ │ │ ├── api.preview.tsx │ │ │ ├── blogs.$blogHandle.$articleHandle.tsx │ │ │ ├── blogs.$blogHandle._index.tsx │ │ │ ├── blogs._index.tsx │ │ │ ├── cart.$lines.tsx │ │ │ ├── cart.tsx │ │ │ ├── collections.$handle.tsx │ │ │ ├── collections._index.tsx │ │ │ ├── collections.all.tsx │ │ │ ├── discount.$code.tsx │ │ │ ├── pages.$handle.tsx │ │ │ ├── policies.$handle.tsx │ │ │ ├── policies._index.tsx │ │ │ ├── products.$handle.tsx │ │ │ ├── search.tsx │ │ │ └── studio.$ │ │ │ │ ├── ClientOnly.tsx │ │ │ │ ├── SanityStudio.client.tsx │ │ │ │ ├── route.tsx │ │ │ │ ├── studio.css │ │ │ │ └── useHydrated.tsx │ │ ├── sanity │ │ │ ├── config.ts │ │ │ └── schema │ │ │ │ └── index.ts │ │ └── styles │ │ │ ├── app.css │ │ │ └── reset.css │ ├── customer-accountapi.generated.d.ts │ ├── env.d.ts │ ├── package.json │ ├── public │ │ └── .gitkeep │ ├── sanity.cli.ts │ ├── sanity.config.ts │ ├── server.ts │ ├── storefrontapi.generated.d.ts │ ├── tsconfig.json │ └── vite.config.ts ├── remix-storefront │ ├── .env.example │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .graphqlrc.yml │ ├── .npmrc │ ├── README.md │ ├── app │ │ ├── assets │ │ │ └── favicon.svg │ │ ├── components │ │ │ ├── Aside.tsx │ │ │ ├── Cart.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Header.tsx │ │ │ ├── Layout.tsx │ │ │ └── Search.tsx │ │ ├── entry.client.tsx │ │ ├── entry.server.tsx │ │ ├── graphql │ │ │ └── customer-account │ │ │ │ ├── CustomerAddressMutations.ts │ │ │ │ ├── CustomerDetailsQuery.ts │ │ │ │ ├── CustomerOrderQuery.ts │ │ │ │ ├── CustomerOrdersQuery.ts │ │ │ │ └── CustomerUpdateMutation.ts │ │ ├── lib │ │ │ ├── fragments.ts │ │ │ ├── root-data.ts │ │ │ ├── search.ts │ │ │ ├── session.ts │ │ │ └── variants.ts │ │ ├── root.tsx │ │ ├── routes │ │ │ ├── $.tsx │ │ │ ├── [robots.txt].tsx │ │ │ ├── [sitemap.xml].tsx │ │ │ ├── _index.tsx │ │ │ ├── account.$.tsx │ │ │ ├── account._index.tsx │ │ │ ├── account.addresses.tsx │ │ │ ├── account.orders.$id.tsx │ │ │ ├── account.orders._index.tsx │ │ │ ├── account.profile.tsx │ │ │ ├── account.tsx │ │ │ ├── account_.authorize.tsx │ │ │ ├── account_.login.tsx │ │ │ ├── account_.logout.tsx │ │ │ ├── api.predictive-search.tsx │ │ │ ├── blogs.$blogHandle.$articleHandle.tsx │ │ │ ├── blogs.$blogHandle._index.tsx │ │ │ ├── blogs._index.tsx │ │ │ ├── cart.$lines.tsx │ │ │ ├── cart.tsx │ │ │ ├── collections.$handle.tsx │ │ │ ├── collections._index.tsx │ │ │ ├── collections.all.tsx │ │ │ ├── discount.$code.tsx │ │ │ ├── pages.$handle.tsx │ │ │ ├── policies.$handle.tsx │ │ │ ├── policies._index.tsx │ │ │ ├── products.$handle.tsx │ │ │ └── search.tsx │ │ └── styles │ │ │ ├── app.css │ │ │ └── reset.css │ ├── customer-accountapi.generated.d.ts │ ├── package.json │ ├── public │ │ └── .gitkeep │ ├── remix.config.js │ ├── remix.env.d.ts │ ├── server.ts │ ├── storefrontapi.generated.d.ts │ └── tsconfig.json └── vite-storefront │ ├── .env.example │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .graphqlrc.yml │ ├── .npmrc │ ├── CHANGELOG.md │ ├── README.md │ ├── app │ ├── assets │ │ └── favicon.svg │ ├── components │ │ ├── Aside.tsx │ │ ├── Cart.tsx │ │ ├── Footer.tsx │ │ ├── Header.tsx │ │ ├── Layout.tsx │ │ └── Search.tsx │ ├── entry.client.tsx │ ├── entry.server.tsx │ ├── graphql │ │ └── customer-account │ │ │ ├── CustomerAddressMutations.ts │ │ │ ├── CustomerDetailsQuery.ts │ │ │ ├── CustomerOrderQuery.ts │ │ │ ├── CustomerOrdersQuery.ts │ │ │ └── CustomerUpdateMutation.ts │ ├── lib │ │ ├── fragments.ts │ │ ├── root-data.ts │ │ ├── search.ts │ │ ├── session.ts │ │ └── variants.ts │ ├── root.tsx │ ├── routes │ │ ├── $.tsx │ │ ├── [robots.txt].tsx │ │ ├── [sitemap.xml].tsx │ │ ├── _index.tsx │ │ ├── account.$.tsx │ │ ├── account._index.tsx │ │ ├── account.addresses.tsx │ │ ├── account.orders.$id.tsx │ │ ├── account.orders._index.tsx │ │ ├── account.profile.tsx │ │ ├── account.tsx │ │ ├── account_.authorize.tsx │ │ ├── account_.login.tsx │ │ ├── account_.logout.tsx │ │ ├── api.predictive-search.tsx │ │ ├── blogs.$blogHandle.$articleHandle.tsx │ │ ├── blogs.$blogHandle._index.tsx │ │ ├── blogs._index.tsx │ │ ├── cart.$lines.tsx │ │ ├── cart.tsx │ │ ├── collections.$handle.tsx │ │ ├── collections._index.tsx │ │ ├── collections.all.tsx │ │ ├── discount.$code.tsx │ │ ├── pages.$handle.tsx │ │ ├── policies.$handle.tsx │ │ ├── policies._index.tsx │ │ ├── products.$handle.tsx │ │ └── search.tsx │ └── styles │ │ ├── app.css │ │ └── reset.css │ ├── customer-accountapi.generated.d.ts │ ├── env.d.ts │ ├── package.json │ ├── public │ └── .gitkeep │ ├── server.ts │ ├── storefrontapi.generated.d.ts │ ├── tsconfig.json │ └── vite.config.ts ├── lint-staged.base.js ├── lint-staged.config.js ├── package.json ├── package ├── .eslintignore ├── .eslintrc ├── .releaserc.json ├── CHANGELOG.md ├── LICENSE ├── MIGRATE-v3-to-v4.md ├── README.md ├── lint-staged.config.js ├── package.config.ts ├── package.json ├── src │ ├── client.ts │ ├── context.test.ts │ ├── context.ts │ ├── groq.ts │ ├── index.ts │ ├── loader.test.ts │ ├── loader.ts │ ├── preview │ │ ├── route.test.ts │ │ └── route.ts │ ├── utils.ts │ └── visual-editing │ │ ├── VisualEditing.client.tsx │ │ ├── VisualEditing.tsx │ │ └── index.ts ├── tsconfig.dist.json ├── tsconfig.json ├── tsconfig.settings.json ├── vitest.config.ts └── vitest.setup.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── turbo.json └── vitest.workspace.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | ; editorconfig.org 2 | root = true 3 | charset= utf8 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["local>sanity-io/renovate-config"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # macOS finder cache file 40 | .DS_Store 41 | 42 | # VS Code settings 43 | .vscode 44 | 45 | # IntelliJ 46 | .idea 47 | *.iml 48 | 49 | # Cache 50 | .cache 51 | .eslintcache 52 | 53 | # Yalc 54 | .yalc 55 | yalc.lock 56 | 57 | # npm package zips 58 | *.tgz 59 | 60 | # Compiled plugin 61 | dist 62 | 63 | # Turbo 64 | .turbo -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | pnpx commitlint --edit $1 -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpx lint-staged -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # Adjust log level 2 | # https://docs.npmjs.com/cli/v8/using-npm/config#loglevel 3 | loglevel="warn" 4 | 5 | # Disable audit reports 6 | # https://docs.npmjs.com/cli/v8/using-npm/config#audit 7 | audit=false 8 | 9 | # Disable funding message 10 | # https://docs.npmjs.com/cli/v8/using-npm/config#fund 11 | fund=false 12 | 13 | # Disable progress bar 14 | progress=false 15 | 16 | strict-peer-deps=true 17 | 18 | # Ensure Vite can optimize these deps in PNPM 19 | public-hoist-pattern[]=cookie 20 | public-hoist-pattern[]=set-cookie-parser 21 | public-hoist-pattern[]=content-security-policy-builder 22 | 23 | workspaces-update=false -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | pnpm-lock.yaml 3 | yarn.lock 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "printWidth": 100, 4 | "bracketSpacing": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Sanity.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./package/README.md -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ['@commitlint/config-conventional'], 3 | } 4 | -------------------------------------------------------------------------------- /examples/embedded-studio/.env.example: -------------------------------------------------------------------------------- 1 | # These variables are only available locally in MiniOxygen 2 | # https://shopify.dev/docs/custom-storefronts/hydrogen/environment-variables 3 | 4 | # Shopify environment variables... 5 | SESSION_SECRET= 6 | PUBLIC_STOREFRONT_ID= 7 | PUBLIC_STOREFRONT_API_TOKEN= 8 | PUBLIC_STORE_DOMAIN= 9 | PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID= 10 | PUBLIC_CUSTOMER_ACCOUNT_API_URL= 11 | PRIVATE_STOREFRONT_API_TOKEN= 12 | 13 | # Sanity environment variables... 14 | 15 | # Project ID 16 | PUBLIC_SANITY_PROJECT_ID= 17 | 18 | # (Optional) Dataset name 19 | # Defaults to `production` 20 | # PUBLIC_SANITY_DATASET= 21 | 22 | # (Optional) Sanity API version 23 | # Defaults to `v2022-03-07` 24 | # PUBLIC_SANITY_API_VERSION= 25 | 26 | # Sanity token to authenticate requests in "preview" mode 27 | # Only requires 'viewer' role 28 | # https://www.sanity.io/docs/http-auth 29 | SANITY_PREVIEW_TOKEN= -------------------------------------------------------------------------------- /examples/embedded-studio/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | bin 4 | *.d.ts 5 | dist 6 | -------------------------------------------------------------------------------- /examples/embedded-studio/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import("@types/eslint").Linter.BaseConfig} 3 | */ 4 | module.exports = { 5 | extends: [ 6 | '@remix-run/eslint-config', 7 | 'plugin:hydrogen/recommended', 8 | 'plugin:hydrogen/typescript', 9 | ], 10 | rules: { 11 | '@typescript-eslint/ban-ts-comment': 'off', 12 | '@typescript-eslint/naming-convention': 'off', 13 | 'hydrogen/prefer-image-component': 'off', 14 | 'no-useless-escape': 'off', 15 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', 16 | 'no-case-declarations': 'off', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /examples/embedded-studio/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /.cache 3 | /build 4 | /dist 5 | /public/build 6 | /.mf 7 | .env 8 | .shopify 9 | -------------------------------------------------------------------------------- /examples/embedded-studio/.graphqlrc.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | default: 3 | schema: 'node_modules/@shopify/hydrogen/storefront.schema.json' 4 | documents: 5 | - '!*.d.ts' 6 | - '*.{ts,tsx,js,jsx}' 7 | - 'app/**/*.{ts,tsx,js,jsx}' 8 | - '!app/graphql/**/*.{ts,tsx,js,jsx}' 9 | customer-account: 10 | schema: 'node_modules/@shopify/hydrogen/customer-account.schema.json' 11 | documents: 12 | - 'app/graphql/customer-account/**/*.{ts,tsx,js,jsx}' 13 | -------------------------------------------------------------------------------- /examples/embedded-studio/.npmrc: -------------------------------------------------------------------------------- 1 | @shopify:registry=https://registry.npmjs.com 2 | progress=false 3 | 4 | # Ensure Vite can optimize these deps in PNPM 5 | public-hoist-pattern[]=cookie 6 | public-hoist-pattern[]=set-cookie-parser 7 | public-hoist-pattern[]=content-security-policy-builder 8 | -------------------------------------------------------------------------------- /examples/embedded-studio/app/assets/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 23 | 28 | 29 | -------------------------------------------------------------------------------- /examples/embedded-studio/app/components/Aside.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * A side bar component with Overlay that works without JavaScript. 3 | * @example 4 | * ```jsx 5 | * 9 | * ``` 10 | */ 11 | export function Aside({ 12 | children, 13 | heading, 14 | id = 'aside', 15 | }: { 16 | children?: React.ReactNode; 17 | heading: React.ReactNode; 18 | id?: string; 19 | }) { 20 | return ( 21 | 37 | ); 38 | } 39 | 40 | function CloseAside() { 41 | return ( 42 | /* eslint-disable-next-line jsx-a11y/anchor-is-valid */ 43 | history.go(-1)}> 44 | × 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /examples/embedded-studio/app/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import {NavLink} from '@remix-run/react'; 2 | import type {FooterQuery, HeaderQuery} from 'storefrontapi.generated'; 3 | import {useRootLoaderData} from '~/lib/root-data'; 4 | 5 | export function Footer({ 6 | menu, 7 | shop, 8 | }: FooterQuery & {shop: HeaderQuery['shop']}) { 9 | return ( 10 | 15 | ); 16 | } 17 | 18 | function FooterMenu({ 19 | menu, 20 | primaryDomainUrl, 21 | }: { 22 | menu: FooterQuery['menu']; 23 | primaryDomainUrl: HeaderQuery['shop']['primaryDomain']['url']; 24 | }) { 25 | const {publicStoreDomain} = useRootLoaderData(); 26 | 27 | return ( 28 | 56 | ); 57 | } 58 | 59 | const FALLBACK_FOOTER_MENU = { 60 | id: 'gid://shopify/Menu/199655620664', 61 | items: [ 62 | { 63 | id: 'gid://shopify/MenuItem/461633060920', 64 | resourceId: 'gid://shopify/ShopPolicy/23358046264', 65 | tags: [], 66 | title: 'Privacy Policy', 67 | type: 'SHOP_POLICY', 68 | url: '/policies/privacy-policy', 69 | items: [], 70 | }, 71 | { 72 | id: 'gid://shopify/MenuItem/461633093688', 73 | resourceId: 'gid://shopify/ShopPolicy/23358013496', 74 | tags: [], 75 | title: 'Refund Policy', 76 | type: 'SHOP_POLICY', 77 | url: '/policies/refund-policy', 78 | items: [], 79 | }, 80 | { 81 | id: 'gid://shopify/MenuItem/461633126456', 82 | resourceId: 'gid://shopify/ShopPolicy/23358111800', 83 | tags: [], 84 | title: 'Shipping Policy', 85 | type: 'SHOP_POLICY', 86 | url: '/policies/shipping-policy', 87 | items: [], 88 | }, 89 | { 90 | id: 'gid://shopify/MenuItem/461633159224', 91 | resourceId: 'gid://shopify/ShopPolicy/23358079032', 92 | tags: [], 93 | title: 'Terms of Service', 94 | type: 'SHOP_POLICY', 95 | url: '/policies/terms-of-service', 96 | items: [], 97 | }, 98 | ], 99 | }; 100 | 101 | function activeLinkStyle({ 102 | isActive, 103 | isPending, 104 | }: { 105 | isActive: boolean; 106 | isPending: boolean; 107 | }) { 108 | return { 109 | fontWeight: isActive ? 'bold' : undefined, 110 | color: isPending ? 'grey' : 'white', 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /examples/embedded-studio/app/components/Layout.tsx: -------------------------------------------------------------------------------- 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/Cart'; 12 | import { 13 | PredictiveSearchForm, 14 | PredictiveSearchResults, 15 | } from '~/components/Search'; 16 | 17 | export type LayoutProps = { 18 | cart: Promise; 19 | children?: React.ReactNode; 20 | footer: Promise; 21 | header: HeaderQuery; 22 | isLoggedIn: Promise; 23 | }; 24 | 25 | export function Layout({ 26 | cart, 27 | children = null, 28 | footer, 29 | header, 30 | isLoggedIn, 31 | }: LayoutProps) { 32 | return ( 33 | <> 34 | 35 | 36 | 37 | {header &&
} 38 |
{children}
39 | 40 | 41 | {(footer) =>