From 74811bd7914fbefba11d35d03c3f682bf0d9c578 Mon Sep 17 00:00:00 2001 From: Bret Little Date: Fri, 6 Oct 2023 12:22:15 -0400 Subject: [PATCH 1/6] Add a tool for previewing shopify.dev reference docs --- package-lock.json | 568 ++++++++++++++++++++- package.json | 3 + packages/docs-preview/.gitignore | 140 +++++ packages/docs-preview/README.md | 11 + packages/docs-preview/app/entry.client.tsx | 18 + packages/docs-preview/app/entry.server.tsx | 137 +++++ packages/docs-preview/app/root.tsx | 106 ++++ packages/docs-preview/app/routes/$doc.tsx | 131 +++++ packages/docs-preview/app/tailwind.css | 3 + packages/docs-preview/bin/cli.js | 75 +++ packages/docs-preview/package.json | 36 ++ packages/docs-preview/public/favicon.ico | Bin 0 -> 16958 bytes packages/docs-preview/remix.config.js | 18 + packages/docs-preview/remix.env.d.ts | 2 + packages/docs-preview/tailwind.config.js | 7 + packages/docs-preview/tailwind.config.ts | 9 + packages/docs-preview/tsconfig.json | 24 + packages/hydrogen-react/package.json | 3 +- packages/hydrogen/package.json | 3 +- 19 files changed, 1281 insertions(+), 13 deletions(-) create mode 100644 packages/docs-preview/.gitignore create mode 100644 packages/docs-preview/README.md create mode 100644 packages/docs-preview/app/entry.client.tsx create mode 100644 packages/docs-preview/app/entry.server.tsx create mode 100644 packages/docs-preview/app/root.tsx create mode 100644 packages/docs-preview/app/routes/$doc.tsx create mode 100644 packages/docs-preview/app/tailwind.css create mode 100644 packages/docs-preview/bin/cli.js create mode 100644 packages/docs-preview/package.json create mode 100644 packages/docs-preview/public/favicon.ico create mode 100644 packages/docs-preview/remix.config.js create mode 100644 packages/docs-preview/remix.env.d.ts create mode 100644 packages/docs-preview/tailwind.config.js create mode 100644 packages/docs-preview/tailwind.config.ts create mode 100644 packages/docs-preview/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 7dbc0127b9..082a41b50b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "templates/demo-store", "templates/skeleton", "examples/express", + "examples/customer-api", + "examples/bun", + "packages/docs-preview", "packages/remix-oxygen", "packages/hydrogen", "packages/create-hydrogen", @@ -38,6 +41,36 @@ "node": ">=16.13" } }, + "examples/customer-api": { + "version": "0.0.0", + "dependencies": { + "@remix-run/react": "1.19.1", + "@shopify/cli": "3.49.2", + "@shopify/cli-hydrogen": "^5.4.3", + "@shopify/hydrogen": "^2023.7.11", + "@shopify/remix-oxygen": "^1.1.1", + "graphql": "^16.6.0", + "graphql-tag": "^2.12.6", + "isbot": "^3.6.6", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@remix-run/dev": "1.19.1", + "@shopify/oxygen-workers-types": "^3.17.2", + "@shopify/prettier-config": "^1.1.2", + "@types/eslint": "^8.4.10", + "@types/react": "^18.2.22", + "@types/react-dom": "^18.2.7", + "eslint": "^8.20.0", + "eslint-plugin-hydrogen": "0.12.2", + "prettier": "^2.8.4", + "typescript": "^5.2.2" + }, + "engines": { + "node": ">=16.13" + } + }, "examples/express": { "name": "hydrogen-express", "dependencies": { @@ -7234,6 +7267,24 @@ "node": ">=14" } }, + "node_modules/@remix-run/serve": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@remix-run/serve/-/serve-1.19.1.tgz", + "integrity": "sha512-ebymSwdykLH+Dtyg7RQDfL0g6Mh2ev/aiMYVPHVJewQmuThDJUmvASFG6uEqY9znDgi4yiBSywbRen89uuilSg==", + "dependencies": { + "@remix-run/express": "1.19.1", + "@remix-run/node": "1.19.1", + "compression": "^1.7.4", + "express": "^4.17.1", + "morgan": "^1.10.0" + }, + "bin": { + "remix-serve": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@remix-run/server-runtime": { "version": "1.19.1", "license": "MIT", @@ -8999,12 +9050,17 @@ }, "node_modules/@types/hast": { "version": "2.3.4", - "devOptional": true, "license": "MIT", "dependencies": { "@types/unist": "*" } }, + "node_modules/@types/he": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.1.tgz", + "integrity": "sha512-CdNmJMcSqX1BiP3iSsWt+VgixndRIDGzWyaGpBnW3i5heATSk5bJu2j3buutsoBQNjyryqxaNpr8M7fRsGL15w==", + "dev": true + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.2.tgz", @@ -9237,6 +9293,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.7.tgz", + "integrity": "sha512-bo5fEO5toQeyCp0zVHBeggclqf5SQ/Z5blfFmjwO5dkMVGPgmiwZsJh9nu/Bo5L7IHTuGWrja6LxJVE2uB5ZrQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/readdir-glob": { "version": "1.1.1", "license": "MIT", @@ -9356,7 +9421,6 @@ }, "node_modules/@types/unist": { "version": "2.0.6", - "devOptional": true, "license": "MIT" }, "node_modules/@types/ws": { @@ -12890,6 +12954,10 @@ "dev": true, "license": "MIT" }, + "node_modules/customer-api": { + "resolved": "examples/customer-api", + "link": true + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "dev": true, @@ -13398,6 +13466,10 @@ "dev": true, "license": "MIT" }, + "node_modules/docs-preview": { + "resolved": "packages/docs-preview", + "link": true + }, "node_modules/doctrine": { "version": "3.0.0", "dev": true, @@ -15401,7 +15473,6 @@ }, "node_modules/format": { "version": "0.2.2", - "devOptional": true, "engines": { "node": ">=0.4.x" } @@ -16385,7 +16456,6 @@ }, "node_modules/he": { "version": "1.2.0", - "dev": true, "license": "MIT", "bin": { "he": "bin/he" @@ -16408,6 +16478,14 @@ "resolved": "templates/hello-world", "link": true }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, "node_modules/history": { "version": "5.3.0", "dev": true, @@ -18856,6 +18934,31 @@ "node": ">=8" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lowlight/node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "license": "ISC", @@ -18953,6 +19056,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/marked": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.0.tgz", + "integrity": "sha512-VZjm0PM5DMv7WodqOUps3g6Q7dmxs9YGiFUZ7a2majzQTTCgX+6S6NAJHPvOhgFBzYz8s4QZKWWMfZKFmsfOgA==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, "node_modules/mdast-util-definitions": { "version": "5.1.1", "devOptional": true, @@ -25681,6 +25795,14 @@ "react": ">=0.14.9" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "license": "MIT" @@ -26023,6 +26145,21 @@ "react-dom": ">=16.8" } }, + "node_modules/react-syntax-highlighter": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", + "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/react-universal-interface": { "version": "0.6.2", "peerDependencies": { @@ -26366,6 +26503,167 @@ "esprima": "~4.0.0" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/refractor/node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/regenerate": { "version": "1.4.2", "devOptional": true, @@ -30917,6 +31215,33 @@ "create-hydrogen": "dist/create-app.js" } }, + "packages/docs-preview": { + "dependencies": { + "@remix-run/css-bundle": "1.19.1", + "@remix-run/node": "1.19.1", + "@remix-run/react": "1.19.1", + "@remix-run/serve": "1.19.1", + "he": "^1.2.0", + "isbot": "^3.6.8", + "marked": "^9.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-syntax-highlighter": "^15.5.0" + }, + "devDependencies": { + "@remix-run/dev": "1.19.1", + "@remix-run/eslint-config": "1.19.1", + "@types/he": "^1.2.1", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", + "@types/react-syntax-highlighter": "^15.5.7", + "eslint": "^8.38.0", + "typescript": "^5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } + }, "packages/hydrogen": { "name": "@shopify/hydrogen", "version": "2023.7.11", @@ -36093,6 +36418,18 @@ "@remix-run/router": { "version": "1.7.2" }, + "@remix-run/serve": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@remix-run/serve/-/serve-1.19.1.tgz", + "integrity": "sha512-ebymSwdykLH+Dtyg7RQDfL0g6Mh2ev/aiMYVPHVJewQmuThDJUmvASFG6uEqY9znDgi4yiBSywbRen89uuilSg==", + "requires": { + "@remix-run/express": "1.19.1", + "@remix-run/node": "1.19.1", + "compression": "^1.7.4", + "express": "^4.17.1", + "morgan": "^1.10.0" + } + }, "@remix-run/server-runtime": { "version": "1.19.1", "requires": { @@ -37743,11 +38080,16 @@ }, "@types/hast": { "version": "2.3.4", - "devOptional": true, "requires": { "@types/unist": "*" } }, + "@types/he": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.1.tgz", + "integrity": "sha512-CdNmJMcSqX1BiP3iSsWt+VgixndRIDGzWyaGpBnW3i5heATSk5bJu2j3buutsoBQNjyryqxaNpr8M7fRsGL15w==", + "dev": true + }, "@types/http-cache-semantics": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.2.tgz", @@ -37938,6 +38280,15 @@ "@types/react": "*" } }, + "@types/react-syntax-highlighter": { + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.7.tgz", + "integrity": "sha512-bo5fEO5toQeyCp0zVHBeggclqf5SQ/Z5blfFmjwO5dkMVGPgmiwZsJh9nu/Bo5L7IHTuGWrja6LxJVE2uB5ZrQ==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/readdir-glob": { "version": "1.1.1", "requires": { @@ -38042,8 +38393,7 @@ "version": "2.0.2" }, "@types/unist": { - "version": "2.0.6", - "devOptional": true + "version": "2.0.6" }, "@types/ws": { "version": "8.5.7", @@ -40184,6 +40534,31 @@ "version": "5.6.5", "dev": true }, + "customer-api": { + "version": "file:examples/customer-api", + "requires": { + "@remix-run/dev": "1.19.1", + "@remix-run/react": "1.19.1", + "@shopify/cli": "3.49.2", + "@shopify/cli-hydrogen": "^5.4.3", + "@shopify/hydrogen": "^2023.7.11", + "@shopify/oxygen-workers-types": "^3.17.2", + "@shopify/prettier-config": "^1.1.2", + "@shopify/remix-oxygen": "^1.1.1", + "@types/eslint": "^8.4.10", + "@types/react": "^18.2.22", + "@types/react-dom": "^18.2.7", + "eslint": "^8.20.0", + "eslint-plugin-hydrogen": "0.12.2", + "graphql": "^16.6.0", + "graphql-tag": "^2.12.6", + "isbot": "^3.6.6", + "prettier": "^2.8.4", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "typescript": "^5.2.2" + } + }, "damerau-levenshtein": { "version": "1.0.8", "dev": true @@ -40544,6 +40919,29 @@ "version": "1.1.3", "dev": true }, + "docs-preview": { + "version": "file:packages/docs-preview", + "requires": { + "@remix-run/css-bundle": "1.19.1", + "@remix-run/dev": "1.19.1", + "@remix-run/eslint-config": "1.19.1", + "@remix-run/node": "1.19.1", + "@remix-run/react": "1.19.1", + "@remix-run/serve": "1.19.1", + "@types/he": "^1.2.1", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", + "@types/react-syntax-highlighter": "^15.5.7", + "eslint": "^8.38.0", + "he": "^1.2.0", + "isbot": "^3.6.8", + "marked": "^9.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-syntax-highlighter": "^15.5.0", + "typescript": "^5.1.6" + } + }, "doctrine": { "version": "3.0.0", "dev": true, @@ -41885,8 +42283,7 @@ "version": "2.1.4" }, "format": { - "version": "0.2.2", - "devOptional": true + "version": "0.2.2" }, "formdata-polyfill": { "version": "4.0.10", @@ -42524,8 +42921,7 @@ } }, "he": { - "version": "1.2.0", - "dev": true + "version": "1.2.0" }, "header-case": { "version": "2.0.4", @@ -42567,6 +42963,11 @@ "typescript": "^5.2.2" } }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" + }, "history": { "version": "5.3.0", "dev": true, @@ -44073,6 +44474,25 @@ "version": "2.0.0", "devOptional": true }, + "lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "requires": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "dependencies": { + "fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "requires": { + "format": "^0.2.0" + } + } + } + }, "lru-cache": { "version": "6.0.0", "requires": { @@ -44130,6 +44550,11 @@ "version": "3.0.3", "dev": true }, + "marked": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.0.tgz", + "integrity": "sha512-VZjm0PM5DMv7WodqOUps3g6Q7dmxs9YGiFUZ7a2majzQTTCgX+6S6NAJHPvOhgFBzYz8s4QZKWWMfZKFmsfOgA==" + }, "mdast-util-definitions": { "version": "5.1.1", "devOptional": true, @@ -48206,6 +48631,11 @@ "dev": true, "requires": {} }, + "prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==" + }, "process-nextick-args": { "version": "2.0.1" }, @@ -48424,6 +48854,18 @@ "react-router": "6.14.2" } }, + "react-syntax-highlighter": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", + "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "requires": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + } + }, "react-universal-interface": { "version": "0.6.2", "requires": {} @@ -48664,6 +49106,110 @@ "esprima": "~4.0.0" } }, + "refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "requires": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "dependencies": { + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" + }, + "comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==" + }, + "hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==" + }, + "hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + } + }, + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" + }, + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" + }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" + }, + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, + "prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==" + }, + "property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "requires": { + "xtend": "^4.0.0" + } + }, + "space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==" + } + } + }, "regenerate": { "version": "1.4.2", "devOptional": true diff --git a/package.json b/package.json index f9f98b5339..0a0eeccb4e 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,9 @@ "templates/demo-store", "templates/skeleton", "examples/express", + "examples/customer-api", + "examples/bun", + "packages/docs-preview", "packages/remix-oxygen", "packages/hydrogen", "packages/create-hydrogen", diff --git a/packages/docs-preview/.gitignore b/packages/docs-preview/.gitignore new file mode 100644 index 0000000000..35ee339bb7 --- /dev/null +++ b/packages/docs-preview/.gitignore @@ -0,0 +1,140 @@ +node_modules + +/.mf +.env +.vscode +.turbo +.cache +**/dist +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +.env.test +!**/playground/**/.env + +# parcel-bundler cache (https://parceljs.org/) +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# VSCode launch configs +.vscode/ +!examples/*/.vscode/ +!templates/*/.vscode/ + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# User localdev package +packages/localdev + +.DS_Store + +temp +snow-devil +artifacts + +yarn.lock +!/yarn.lock + +admin.schema.json +business-platform.schema.json + +app/generated_docs_data.js +public/build +build \ No newline at end of file diff --git a/packages/docs-preview/README.md b/packages/docs-preview/README.md new file mode 100644 index 0000000000..3b56fbb48a --- /dev/null +++ b/packages/docs-preview/README.md @@ -0,0 +1,11 @@ +# Docs Previewer + +This tool makes it easier to preview reference docs that are deployed to shopify.dev. + +## Usage + +Run the CLI with a path to the docs metadata file, and an app will boot up rendering a preview of the docs. + +```bash +node docs-preview/bin/cli.js path/to/generated_docs_data.json +``` diff --git a/packages/docs-preview/app/entry.client.tsx b/packages/docs-preview/app/entry.client.tsx new file mode 100644 index 0000000000..0d53161a36 --- /dev/null +++ b/packages/docs-preview/app/entry.client.tsx @@ -0,0 +1,18 @@ +/** + * By default, Remix will handle hydrating your app on the client for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.client + */ + +import {RemixBrowser} from '@remix-run/react'; +import {startTransition, StrictMode} from 'react'; +import {hydrateRoot} from 'react-dom/client'; + +startTransition(() => { + hydrateRoot( + document, + + + , + ); +}); diff --git a/packages/docs-preview/app/entry.server.tsx b/packages/docs-preview/app/entry.server.tsx new file mode 100644 index 0000000000..a5ce794a66 --- /dev/null +++ b/packages/docs-preview/app/entry.server.tsx @@ -0,0 +1,137 @@ +/** + * By default, Remix will handle generating the HTTP Response for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.server + */ + +import {PassThrough} from 'node:stream'; + +import type {AppLoadContext, EntryContext} from '@remix-run/node'; +import {createReadableStreamFromReadable} from '@remix-run/node'; +import {RemixServer} from '@remix-run/react'; +import isbot from 'isbot'; +import {renderToPipeableStream} from 'react-dom/server'; + +const ABORT_DELAY = 5_000; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + loadContext: AppLoadContext, +) { + return isbot(request.headers.get('user-agent')) + ? handleBotRequest( + request, + responseStatusCode, + responseHeaders, + remixContext, + ) + : handleBrowserRequest( + request, + responseStatusCode, + responseHeaders, + remixContext, + ); +} + +function handleBotRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const {pipe, abort} = renderToPipeableStream( + , + { + onAllReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + }, + ); + + setTimeout(abort, ABORT_DELAY); + }); +} + +function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const {pipe, abort} = renderToPipeableStream( + , + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + }, + ); + + setTimeout(abort, ABORT_DELAY); + }); +} diff --git a/packages/docs-preview/app/root.tsx b/packages/docs-preview/app/root.tsx new file mode 100644 index 0000000000..a07fee0739 --- /dev/null +++ b/packages/docs-preview/app/root.tsx @@ -0,0 +1,106 @@ +import {json, type LinksFunction} from '@remix-run/node'; +import { + Links, + LiveReload, + Meta, + NavLink, + Outlet, + Scripts, + ScrollRestoration, + useLoaderData, + useParams, +} from '@remix-run/react'; +import stylesheet from '~/tailwind.css'; +import {Fragment, useCallback, useState} from 'react'; +import he from 'he'; +import data from './generated_docs_data'; + +export const links: LinksFunction = () => [ + {rel: 'stylesheet', href: stylesheet}, +]; + +export async function loader() { + for (const doc of data) { + for (const tab of doc.defaultExample.codeblock.tabs) { + tab.code = he.decode(tab.code); + } + } + + return json({ + data, + }); +} + +export default function App() { + const {data} = useLoaderData(); + // group the data by category + const categories = data.reduce((acc: Record, doc: any) => { + if (!acc[doc.category]) { + acc[doc.category] = []; + } + acc[doc.category].push(doc); + return acc; + }, {}); + + return ( + + + + + + + + +
+
+ {Object.keys(categories).map((category) => ( + + ))} +
+
+ +
+
+ + + + + + ); +} + +function Category({name, category}: {name: string; category: any[]}) { + const {doc} = useParams(); + const defaultExpanded = useCallback(() => { + return category.find((categoryDoc) => categoryDoc.name === doc); + }, [doc, category]); + const [expanded, setExpanded] = useState(defaultExpanded); + + return ( + + + {expanded ? ( +
+ {category.map((doc: any) => ( + (isActive ? 'underline' : '')} + > + {doc.name} + + ))} +
+ ) : null} +
+ ); +} diff --git a/packages/docs-preview/app/routes/$doc.tsx b/packages/docs-preview/app/routes/$doc.tsx new file mode 100644 index 0000000000..7b0fd839d9 --- /dev/null +++ b/packages/docs-preview/app/routes/$doc.tsx @@ -0,0 +1,131 @@ +import type {LoaderArgs} from '@remix-run/node'; +import {json} from '@remix-run/node'; +import {useLoaderData, useMatches} from '@remix-run/react'; +import {marked} from 'marked'; +import {useState} from 'react'; + +import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'; +// @ts-ignore +import {oneDark} from 'react-syntax-highlighter/dist/cjs/styles/prism/index.js'; + +export async function loader({params}: LoaderArgs) { + return json({doc: params.doc}); +} + +function getDefinition(definitions: any, type: string) { + return definitions[type] ? definitions[type] : {}; +} + +export default function Index() { + const {doc} = useLoaderData(); + const matches = useMatches(); + const data = (matches as any)[0].data.data; + + const docMetaData = data.find((d: any) => d.name === doc!); + const definition = docMetaData.definitions[0]; + const typeDef = definition + ? getDefinition(definition.typeDefinitions, definition.type) + : null; + + const props = typeDef + ? typeDef.members ?? typeDef.params ?? typeDef.value + : null; + + return ( +
+

{docMetaData.name}

+

+ + + + {props ? ( + <> +

{definition.title}

+ {props instanceof Array ? ( +
+ {props.map((prop: any) => ( + + ))} +
+ ) : ( +
+ {props} +
+ )} + + ) : null} +
+ ); +} + +function Prop({ + prop, +}: { + prop: { + name: string; + value: string; + description: string; + isOptional?: boolean; + }; +}) { + return ( +
+
+
{prop.name}
+
+ {prop.value} +
+
required
+
+

+
+ ); +} + +function Examples({ + examples, +}: { + examples: {title: string; code: string; language: string}[]; +}) { + const [selectedExample, setSelectedExample] = useState(0); + + if (!examples) return; + + return ( +
+

Example code

+
+
+ {examples.map((example, index) => ( + + ))} +
+ + {examples[selectedExample].code} + +
+
+ ); +} diff --git a/packages/docs-preview/app/tailwind.css b/packages/docs-preview/app/tailwind.css new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/packages/docs-preview/app/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/docs-preview/bin/cli.js b/packages/docs-preview/bin/cli.js new file mode 100644 index 0000000000..ce77cf75e3 --- /dev/null +++ b/packages/docs-preview/bin/cli.js @@ -0,0 +1,75 @@ +const {readFile, watch, writeFile} = require('fs/promises'); +const {spawn} = require('child_process'); +const path = require('path'); + +const docsMetaFile = path.resolve(process.cwd(), process.argv[2]); + +console.log('Watching: ' + docsMetaFile); + +async function run() { + // Copy the metafile to the remix app directory so it can be watched, imported, and loaded by Remix. + copyMetaFile(docsMetaFile); + + // Start up the Remix dev server + const remix = spawn( + 'node', + [ + path.resolve(path.dirname(require.resolve('@remix-run/dev')), 'cli.js'), + 'dev', + '--manual', + ], + { + cwd: path.resolve(__dirname, '..'), + }, + ); + + remix.stdout.on('data', (data) => console.log(`${data}`)); + remix.stdout.on('error', (data) => console.error(`${data}`)); + remix.on('error', (error) => { + console.log('hi'); + process.exit(1); + }); + remix.on('close', (code) => { + if (code > 0) console.error('Remix app failed'); + process.exit(code); + }); + + try { + const watcher = watch(docsMetaFile); + + for await (const event of watcher) { + if (event.eventType === 'change') { + // Whenever the source metafile changes, copy it back into the Remix app dir + // which automatically picks it up and hot reloads the page + copyMetaFile(docsMetaFile); + } + } + } catch (err) { + if (err.name === 'AbortError') return; + console.error('Failed to startup Remix'); + throw err; + } +} + +let timeout; + +function copyMetaFile(file) { + clearTimeout(timeout); + timeout = setTimeout(async () => { + try { + console.log('Copying metafile: ' + file); + const f = await readFile(file, 'utf-8'); + // Read the source metafile so that we can prepend it with `export default` + // There's probably a better way to do this, like JSON imports or write to + // the buffer stream so we don't need to load the entire metafile into memory + writeFile( + path.resolve(__dirname, '../app/generated_docs_data.js'), + 'export default ' + f, + ); + } catch (e) { + console.error('Error copying metafile', e); + } + }, 100); +} + +run(); diff --git a/packages/docs-preview/package.json b/packages/docs-preview/package.json new file mode 100644 index 0000000000..9b02d82dff --- /dev/null +++ b/packages/docs-preview/package.json @@ -0,0 +1,36 @@ +{ + "name": "docs-preview", + "private": true, + "sideEffects": false, + "scripts": { + "build": "remix build", + "dev": "remix dev --manual", + "start": "remix-serve ./build/index.js", + "typecheck": "tsc" + }, + "dependencies": { + "@remix-run/css-bundle": "1.19.1", + "@remix-run/node": "1.19.1", + "@remix-run/react": "1.19.1", + "@remix-run/serve": "1.19.1", + "he": "^1.2.0", + "isbot": "^3.6.8", + "marked": "^9.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-syntax-highlighter": "^15.5.0" + }, + "devDependencies": { + "@remix-run/dev": "1.19.1", + "@remix-run/eslint-config": "1.19.1", + "@types/he": "^1.2.1", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", + "@types/react-syntax-highlighter": "^15.5.7", + "eslint": "^8.38.0", + "typescript": "^5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/packages/docs-preview/public/favicon.ico b/packages/docs-preview/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8830cf6821b354114848e6354889b8ecf6d2bc61 GIT binary patch literal 16958 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 EKbET!0{{R3 literal 0 HcmV?d00001 diff --git a/packages/docs-preview/remix.config.js b/packages/docs-preview/remix.config.js new file mode 100644 index 0000000000..0d71a2fe4c --- /dev/null +++ b/packages/docs-preview/remix.config.js @@ -0,0 +1,18 @@ +/** @type {import('@remix-run/dev').AppConfig} */ +module.exports = { + ignoredRouteFiles: ['**/.*'], + future: { + v2_dev: true, + v2_meta: true, + v2_headers: true, + v2_errorBoundary: true, + v2_routeConvention: true, + v2_normalizeFormMethod: true, + }, + tailwind: true, + serverModuleFormat: 'cjs', + // appDirectory: "app", + // assetsBuildDirectory: "public/build", + // publicPath: "/build/", + // serverBuildPath: "build/index.js", +}; diff --git a/packages/docs-preview/remix.env.d.ts b/packages/docs-preview/remix.env.d.ts new file mode 100644 index 0000000000..dcf8c45e1d --- /dev/null +++ b/packages/docs-preview/remix.env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/packages/docs-preview/tailwind.config.js b/packages/docs-preview/tailwind.config.js new file mode 100644 index 0000000000..c47f1a9545 --- /dev/null +++ b/packages/docs-preview/tailwind.config.js @@ -0,0 +1,7 @@ +module.exports = { + content: ['./app/**/*.{js,jsx,ts,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/packages/docs-preview/tailwind.config.ts b/packages/docs-preview/tailwind.config.ts new file mode 100644 index 0000000000..8aa1ec6d06 --- /dev/null +++ b/packages/docs-preview/tailwind.config.ts @@ -0,0 +1,9 @@ +import type {Config} from 'tailwindcss'; + +module.exports = { + content: ['./app/**/*.{js,jsx,ts,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +} satisfies Config; diff --git a/packages/docs-preview/tsconfig.json b/packages/docs-preview/tsconfig.json new file mode 100644 index 0000000000..b3a178bfee --- /dev/null +++ b/packages/docs-preview/tsconfig.json @@ -0,0 +1,24 @@ +{ + "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "module": "ES2022", + "target": "ES2022", + "strict": true, + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "~/*": ["./app/*"] + }, + + // Remix takes care of building everything in `remix build`. + "noEmit": true + } +} diff --git a/packages/hydrogen-react/package.json b/packages/hydrogen-react/package.json index b09c5bf917..1eebcb70a2 100644 --- a/packages/hydrogen-react/package.json +++ b/packages/hydrogen-react/package.json @@ -125,7 +125,8 @@ "test:watch": "vitest", "typecheck": "run-p typecheck:*", "typecheck:code": "tsc --noEmit", - "typecheck:examples": "tsc --noEmit --project tsconfig.examples.json" + "typecheck:examples": "tsc --noEmit --project tsconfig.examples.json", + "preview-docs": "node ../docs-preview/bin/cli.js docs/generated/generated_docs_data.json" }, "devDependencies": { "@faker-js/faker": "^7.6.0", diff --git a/packages/hydrogen/package.json b/packages/hydrogen/package.json index aeb0577dd9..afe7c4d086 100644 --- a/packages/hydrogen/package.json +++ b/packages/hydrogen/package.json @@ -19,7 +19,8 @@ "prepack": "npm run build", "test:watch": "vitest", "build-docs": "sh ./docs/build-docs.sh && npm run format", - "format": "prettier --write \"{src,docs}/**/*\" --ignore-unknown" + "format": "prettier --write \"{src,docs}/**/*\" --ignore-unknown", + "preview-docs": "node ../docs-preview/bin/cli.js docs/generated/generated_docs_data.json" }, "exports": { ".": { From cb8f7ffb601d9405a73e0031d93668a3e8c71a5e Mon Sep 17 00:00:00 2001 From: Bret Little Date: Fri, 6 Oct 2023 12:28:58 -0400 Subject: [PATCH 2/6] Add a default generated_docs_file --- .../docs-preview/app/generated_docs_data.js | 8328 +++++++++++++++++ 1 file changed, 8328 insertions(+) create mode 100644 packages/docs-preview/app/generated_docs_data.js diff --git a/packages/docs-preview/app/generated_docs_data.js b/packages/docs-preview/app/generated_docs_data.js new file mode 100644 index 0000000000..7c43bae2b4 --- /dev/null +++ b/packages/docs-preview/app/generated_docs_data.js @@ -0,0 +1,8328 @@ +export default [ + { + name: 'AddToCartButton', + category: 'components', + isVisualComponent: false, + related: [], + description: + 'The `AddToCartButton` component renders a button that adds an item to the cart when pressed.\n\nIt must be a descendent of the `CartProvider` component.', + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: "import {AddToCartButton} from '@shopify/hydrogen-react';\n\nexport default function ProductAddToCartButton({product}) {\n const variantId = product.variants[0].id;\n\n if (!variantId) {\n return null;\n }\n\n return <AddToCartButton variantId={variantId} />;\n}\n", + language: 'jsx', + }, + { + title: 'TypeScript', + code: "import {AddToCartButton} from '@shopify/hydrogen-react';\nimport type {Product} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport default function ProductAddToCartButton({product}: {product: Product}) {\n const variantId = product.variants[0].id;\n\n if (!variantId) {\n return null;\n }\n\n return <AddToCartButton variantId={variantId} />;\n}\n", + language: 'tsx', + }, + ], + title: 'Example code', + }, + }, + definitions: [ + { + title: 'Props', + description: '', + type: 'AddToCartButtonPropsForDocs', + typeDefinitions: { + AddToCartButtonPropsForDocs: { + filePath: '/AddToCartButton.tsx', + name: 'AddToCartButtonPropsForDocs', + description: '', + members: [ + { + filePath: '/AddToCartButton.tsx', + syntaxKind: 'PropertySignature', + name: 'attributes', + value: '{ key: string; value: string; }[]', + description: + 'An array of cart line attributes that belong to the item being added to the cart.', + isOptional: true, + }, + { + filePath: '/AddToCartButton.tsx', + syntaxKind: 'PropertySignature', + name: 'variantId', + value: 'string', + description: 'The ID of the variant.', + isOptional: true, + }, + { + filePath: '/AddToCartButton.tsx', + syntaxKind: 'PropertySignature', + name: 'quantity', + value: 'number', + description: 'The item quantity.', + isOptional: true, + }, + { + filePath: '/AddToCartButton.tsx', + syntaxKind: 'PropertySignature', + name: 'accessibleAddingToCartLabel', + value: 'string', + description: + 'The text that is announced by the screen reader when the item is being added to the cart. Used for accessibility purposes only and not displayed on the page.', + isOptional: true, + }, + { + filePath: '/AddToCartButton.tsx', + syntaxKind: 'PropertySignature', + name: 'sellingPlanId', + value: 'string', + description: 'The selling plan ID of the subscription variant', + isOptional: true, + }, + { + filePath: '/AddToCartButton.tsx', + syntaxKind: 'PropertySignature', + name: 'as', + value: 'AsType', + description: + 'Provide a React element or component to render as the underlying button. Note: for accessibility compliance, almost always you should use a `button` element, or a component that renders an underlying button.', + isOptional: true, + }, + { + filePath: '/AddToCartButton.tsx', + syntaxKind: 'PropertySignature', + name: 'children', + value: 'ReactNode', + description: 'Any ReactNode elements.', + }, + { + filePath: '/AddToCartButton.tsx', + syntaxKind: 'PropertySignature', + name: 'onClick', + value: + '(event?: MouseEvent) => boolean | void', + description: + 'Click event handler. Default behaviour triggers unless prevented', + isOptional: true, + }, + { + filePath: '/AddToCartButton.tsx', + syntaxKind: 'PropertySignature', + name: 'defaultOnClick', + value: + '(event?: MouseEvent) => boolean | void', + description: 'A default `onClick` behavior', + isOptional: true, + }, + { + filePath: '/AddToCartButton.tsx', + syntaxKind: 'PropertySignature', + name: 'buttonRef', + value: 'Ref', + description: 'A `ref` to the underlying button', + isOptional: true, + }, + ], + value: + "export interface AddToCartButtonPropsForDocs<\n AsType extends React.ElementType = 'button',\n> extends AddToCartButtonPropsBase,\n CustomBaseButtonProps {}", + }, + }, + }, + ], + }, + { + name: 'BuyNowButton', + category: 'components', + isVisualComponent: false, + related: [], + description: + 'The `BuyNowButton` component renders a button that adds an item to the cart and redirects the customer to checkout.\n\nMust be a child of a `CartProvider` component.\n ', + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: "import {BuyNowButton} from '@shopify/hydrogen-react';\n\nexport default function ProductBuyNowButton({product}) {\n const variantId = product.variants[0].id;\n\n if (!variantId) {\n return null;\n }\n\n return <BuyNowButton variantId={variantId} />;\n}\n", + language: 'jsx', + }, + { + title: 'TypeScript', + code: "import {BuyNowButton} from '@shopify/hydrogen-react';\nimport type {Product} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport default function ProductBuyNowButton({product}: {product: Product}) {\n const variantId = product.variants[0].id;\n\n if (!variantId) {\n return null;\n }\n\n return <BuyNowButton variantId={variantId} />;\n}\n", + language: 'tsx', + }, + ], + title: 'Example code', + }, + }, + definitions: [ + { + title: 'Props', + description: '', + type: 'BuyNowButtonPropsForDocs', + typeDefinitions: { + BuyNowButtonPropsForDocs: { + filePath: '/BuyNowButton.tsx', + name: 'BuyNowButtonPropsForDocs', + description: '', + members: [ + { + filePath: '/BuyNowButton.tsx', + syntaxKind: 'PropertySignature', + name: 'quantity', + value: 'number', + description: 'The item quantity. Defaults to 1.', + isOptional: true, + }, + { + filePath: '/BuyNowButton.tsx', + syntaxKind: 'PropertySignature', + name: 'variantId', + value: 'string', + description: 'The ID of the variant.', + }, + { + filePath: '/BuyNowButton.tsx', + syntaxKind: 'PropertySignature', + name: 'attributes', + value: '{ key: string; value: string; }[]', + description: + 'An array of cart line attributes that belong to the item being added to the cart.', + isOptional: true, + }, + { + filePath: '/BuyNowButton.tsx', + syntaxKind: 'PropertySignature', + name: 'as', + value: 'AsType', + description: + 'Provide a React element or component to render as the underlying button. Note: for accessibility compliance, almost always you should use a `button` element, or a component that renders an underlying button.', + isOptional: true, + }, + { + filePath: '/BuyNowButton.tsx', + syntaxKind: 'PropertySignature', + name: 'children', + value: 'ReactNode', + description: 'Any ReactNode elements.', + }, + { + filePath: '/BuyNowButton.tsx', + syntaxKind: 'PropertySignature', + name: 'onClick', + value: + '(event?: MouseEvent) => boolean | void', + description: + 'Click event handler. Default behaviour triggers unless prevented', + isOptional: true, + }, + { + filePath: '/BuyNowButton.tsx', + syntaxKind: 'PropertySignature', + name: 'defaultOnClick', + value: + '(event?: MouseEvent) => boolean | void', + description: 'A default `onClick` behavior', + isOptional: true, + }, + { + filePath: '/BuyNowButton.tsx', + syntaxKind: 'PropertySignature', + name: 'buttonRef', + value: 'Ref', + description: 'A `ref` to the underlying button', + isOptional: true, + }, + ], + value: + "export interface BuyNowButtonPropsForDocs<\n AsType extends React.ElementType = 'button',\n> extends BuyNowButtonPropsBase,\n CustomBaseButtonProps {}", + }, + }, + }, + ], + }, + { + name: 'CartCheckoutButton', + category: 'components', + isVisualComponent: false, + related: [], + description: + 'The `CartCheckoutButton` component renders a button that redirects to the checkout URL for the cart.\n\nMust be a descendent of a `CartProvider` component.\n ', + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: "import {CartCheckoutButton} from '@shopify/hydrogen-react';\n\nexport default function ProductCartCheckoutButton() {\n return <CartCheckoutButton />;\n}\n", + language: 'jsx', + }, + { + title: 'TypeScript', + code: "import {CartCheckoutButton} from '@shopify/hydrogen-react';\n\nexport default function ProductCartCheckoutButton() {\n return <CartCheckoutButton />;\n}\n", + language: 'tsx', + }, + ], + title: 'Example code', + }, + }, + definitions: [ + { + title: 'Props', + description: '', + type: 'CartCheckoutButtonPropsForDocs', + typeDefinitions: { + CartCheckoutButtonPropsForDocs: { + filePath: '/CartCheckoutButton.tsx', + name: 'CartCheckoutButtonPropsForDocs', + description: '', + members: [ + { + filePath: '/CartCheckoutButton.tsx', + syntaxKind: 'PropertySignature', + name: 'as', + value: 'AsType', + description: + 'Provide a React element or component to render as the underlying button. Note: for accessibility compliance, almost always you should use a `button` element, or a component that renders an underlying button.', + isOptional: true, + }, + { + filePath: '/CartCheckoutButton.tsx', + syntaxKind: 'PropertySignature', + name: 'children', + value: 'ReactNode', + description: 'Any ReactNode elements.', + }, + { + filePath: '/CartCheckoutButton.tsx', + syntaxKind: 'PropertySignature', + name: 'defaultOnClick', + value: + '(event?: MouseEvent) => boolean | void', + description: 'A default `onClick` behavior', + isOptional: true, + }, + { + filePath: '/CartCheckoutButton.tsx', + syntaxKind: 'PropertySignature', + name: 'buttonRef', + value: 'Ref', + description: 'A `ref` to the underlying button', + isOptional: true, + }, + ], + value: + "export interface CartCheckoutButtonPropsForDocs<\n AsType extends React.ElementType = 'button',\n> extends Omit, 'onClick'> {}", + }, + }, + }, + ], + }, + { + name: 'CartCost', + category: 'components', + isVisualComponent: false, + related: [], + description: + '\n The `CartCost` component renders a `Money` component with the cost associated with the `amountType` prop.\n\nIf no `amountType` prop is specified, then it defaults to `totalAmount`.\n\nDepends on `useCart()` and must be a child of ``\n ', + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: 'import {CartCost} from \'@shopify/hydrogen-react\';\n\nexport default function CartTotals() {\n return (\n <>\n <div>\n Subtotal: <CartCost amountType="subtotal" />\n </div>\n <div>\n Tax: <CartCost amountType="tax" />\n </div>\n <div>\n Total: <CartCost />\n </div>\n </>\n );\n}\n', + language: 'jsx', + }, + { + title: 'TypeScript', + code: 'import {CartCost} from \'@shopify/hydrogen-react\';\n\nexport default function CartTotals() {\n return (\n <>\n <div>\n Subtotal: <CartCost amountType="subtotal" />\n </div>\n <div>\n Tax: <CartCost amountType="tax" />\n </div>\n <div>\n Total: <CartCost />\n </div>\n </>\n );\n}\n', + language: 'tsx', + }, + ], + title: 'Example code', + }, + }, + definitions: [ + { + title: 'Props', + description: '', + type: 'CartCostPropsForDocs', + typeDefinitions: { + CartCostPropsForDocs: { + filePath: '/CartCost.tsx', + name: 'CartCostPropsForDocs', + description: '', + members: [ + { + filePath: '/CartCost.tsx', + syntaxKind: 'PropertySignature', + name: 'amountType', + value: '"total" | "subtotal" | "tax" | "duty"', + description: + 'A string type that defines the type of cost needed. Valid values: `total`, `subtotal`, `tax`, or `duty`.', + isOptional: true, + }, + { + filePath: '/CartCost.tsx', + syntaxKind: 'PropertySignature', + name: 'children', + value: 'React.ReactNode', + description: 'Any `ReactNode` elements.', + isOptional: true, + }, + { + filePath: '/CartCost.tsx', + syntaxKind: 'PropertySignature', + name: 'as', + value: 'ComponentGeneric', + description: + 'An HTML tag or React Component to be rendered as the base element wrapper. The default is `div`.', + isOptional: true, + }, + { + filePath: '/CartCost.tsx', + syntaxKind: 'PropertySignature', + name: 'withoutCurrency', + value: 'boolean', + description: + 'Whether to remove the currency symbol from the output.', + isOptional: true, + }, + { + filePath: '/CartCost.tsx', + syntaxKind: 'PropertySignature', + name: 'withoutTrailingZeros', + value: 'boolean', + description: + 'Whether to remove trailing zeros (fractional money) from the output.', + isOptional: true, + }, + { + filePath: '/CartCost.tsx', + syntaxKind: 'PropertySignature', + name: 'measurement', + value: + 'PartialDeep', + description: + 'A [UnitPriceMeasurement object](https://shopify.dev/api/storefront/2023-07/objects/unitpricemeasurement).', + isOptional: true, + }, + { + filePath: '/CartCost.tsx', + syntaxKind: 'PropertySignature', + name: 'measurementSeparator', + value: 'ReactNode', + description: + "Customizes the separator between the money output and the measurement output. Used with the `measurement` prop. Defaults to `'/'`.", + isOptional: true, + }, + ], + value: + "export interface CartCostPropsForDocs\n extends Omit, 'data'>,\n CartCostPropsBase {}", + }, + }, + }, + ], + }, + { + name: 'CartLineProvider', + category: 'components', + isVisualComponent: false, + related: [ + { + name: 'useCartLine', + type: 'hooks', + url: '/api/hydrogen-react/hooks/useCartLine', + }, + ], + description: + '\n The `CartLineProvider` component creates a context for using a cart line.\n ', + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: "import {CartLineProvider, useCartLine} from '@shopify/hydrogen-react';\n\nexport function CartWrapper({cart}) {\n const firstCartLine = cart.lines.nodes[0];\n return (\n <CartLineProvider line={firstCartLine}>\n <CartLineQuantity />\n </CartLineProvider>\n );\n}\n\nfunction CartLineQuantity() {\n const cartLine = useCartLine();\n\n return <div>{cartLine.quantity}</div>;\n}\n", + language: 'jsx', + }, + { + title: 'TypeScript', + code: "import {CartLineProvider, useCartLine} from '@shopify/hydrogen-react';\nimport type {Cart} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport function CartWrapper({cart}: {cart: Cart}) {\n const firstCartLine = cart.lines.nodes[0];\n return (\n <CartLineProvider line={firstCartLine}>\n <CartLineQuantity />\n </CartLineProvider>\n );\n}\n\nfunction CartLineQuantity() {\n const cartLine = useCartLine();\n\n return <div>{cartLine.quantity}</div>;\n}\n", + language: 'tsx', + }, + ], + title: 'Example code', + }, + }, + definitions: [ + { + title: 'Props', + description: '', + type: 'CartLineProviderProps', + typeDefinitions: { + CartLineProviderProps: { + filePath: '/CartLineProvider.tsx', + syntaxKind: 'TypeAliasDeclaration', + name: 'CartLineProviderProps', + value: + '{\n /** Any `ReactNode` elements. */\n children: ReactNode;\n /** A cart line object. */\n line: CartLinePartialDeep;\n}', + description: '', + members: [ + { + filePath: '/CartLineProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'children', + value: 'ReactNode', + description: 'Any `ReactNode` elements.', + }, + { + filePath: '/CartLineProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'line', + value: 'CartLinePartialDeep', + description: 'A cart line object.', + }, + ], + }, + CartLinePartialDeep: { + filePath: '/CartLineProvider.tsx', + syntaxKind: 'TypeAliasDeclaration', + name: 'CartLinePartialDeep', + value: 'CartLinePartialDeep', + description: '', + }, + }, + }, + ], + }, + { + name: 'CartLineQuantity', + category: 'components', + isVisualComponent: false, + related: [ + { + name: 'useCartLine', + type: 'gear', + url: '/api/hydrogen-react/hooks/useCartLine', + }, + { + name: 'CartLineQuantityAdjustButton', + type: 'component', + url: '/api/hydrogen-react/components/CartLineQuantityAdjustButton', + }, + ], + description: + "\n The `` component renders a `span` (or another element / component that can be customized by the `as` prop) with the cart line's quantity.\n\nIt must be a descendent of a `` component, and uses the `useCartLine()` hook internally.\n ", + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: "import {CartLineQuantity, CartLineProvider} from '@shopify/hydrogen-react';\n\nexport function Example({line}) {\n return (\n <CartLineProvider line={line}>\n <CartLineQuantity />\n </CartLineProvider>\n );\n}\n", + language: 'jsx', + }, + { + title: 'TypeScript', + code: "import {CartLineQuantity, CartLineProvider} from '@shopify/hydrogen-react';\nimport type {CartLine} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport function Example({line}: {line: CartLine}) {\n return (\n <CartLineProvider line={line}>\n <CartLineQuantity />\n </CartLineProvider>\n );\n}\n", + language: 'tsx', + }, + ], + title: 'Example code', + }, + }, + definitions: [ + { + title: 'Props', + description: '', + type: 'CartLineQuantityBaseProps', + typeDefinitions: { + CartLineQuantityBaseProps: { + filePath: '/CartLineQuantity.tsx', + name: 'CartLineQuantityBaseProps', + description: '', + members: [ + { + filePath: '/CartLineQuantity.tsx', + syntaxKind: 'PropertySignature', + name: 'as', + value: 'ComponentGeneric', + description: + 'An HTML tag or React Component to be rendered as the base element wrapper. The default is `span`.', + isOptional: true, + }, + ], + value: + "interface CartLineQuantityBaseProps<\n ComponentGeneric extends ElementType = 'span',\n> {\n /** An HTML tag or React Component to be rendered as the base element wrapper. The default is `span`. */\n as?: ComponentGeneric;\n}", + }, + }, + }, + ], + }, + { + name: 'CartLineQuantityAdjustButton', + category: 'components', + isVisualComponent: false, + related: [ + { + name: 'useCartLine', + type: 'gear', + url: '/api/hydrogen-react/hooks/useCartLine', + }, + { + name: 'CartLineQuantity', + type: 'component', + url: '/api/hydrogen-react/components/CartLineQuantity', + }, + ], + description: + "\n The `` component renders a `span` (or another element / component that can be customized by the `as` prop) with the cart line's quantity.\n\nIt must be a descendent of a `` component, and uses the `useCartLine()` hook internally.\n ", + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: 'import {\n CartLineQuantityAdjustButton,\n CartLineProvider,\n CartProvider,\n} from \'@shopify/hydrogen-react\';\n\nexport function Example({line}) {\n return (\n <CartProvider>\n <CartLineProvider line={line}>\n <CartLineQuantityAdjustButton adjust="increase">\n Increase\n </CartLineQuantityAdjustButton>\n <CartLineQuantityAdjustButton adjust="decrease">\n Decrease\n </CartLineQuantityAdjustButton>\n <CartLineQuantityAdjustButton adjust="remove">\n Remove\n </CartLineQuantityAdjustButton>\n </CartLineProvider>\n </CartProvider>\n );\n}\n', + language: 'jsx', + }, + { + title: 'TypeScript', + code: 'import {\n CartLineQuantityAdjustButton,\n CartLineProvider,\n CartProvider,\n} from \'@shopify/hydrogen-react\';\nimport type {CartLine} from \'@shopify/hydrogen-react/storefront-api-types\';\n\nexport function Example({line}: {line: CartLine}) {\n return (\n <CartProvider>\n <CartLineProvider line={line}>\n <CartLineQuantityAdjustButton adjust="increase">\n Increase\n </CartLineQuantityAdjustButton>\n <CartLineQuantityAdjustButton adjust="decrease">\n Decrease\n </CartLineQuantityAdjustButton>\n <CartLineQuantityAdjustButton adjust="remove">\n Remove\n </CartLineQuantityAdjustButton>\n </CartLineProvider>\n </CartProvider>\n );\n}\n', + language: 'tsx', + }, + ], + title: 'Example code', + }, + }, + definitions: [ + { + title: 'Props', + description: '', + type: 'CartLineQuantityAdjustButtonBaseProps', + typeDefinitions: { + CartLineQuantityAdjustButtonBaseProps: { + filePath: '/CartLineQuantityAdjustButton.tsx', + name: 'CartLineQuantityAdjustButtonBaseProps', + description: '', + members: [ + { + filePath: '/CartLineQuantityAdjustButton.tsx', + syntaxKind: 'PropertySignature', + name: 'adjust', + value: '"remove" | "increase" | "decrease"', + description: + "The adjustment for a cart line's quantity. Valid values: `increase` (default), `decrease`, or `remove`.", + isOptional: true, + }, + ], + value: + "interface CartLineQuantityAdjustButtonBaseProps {\n /** The adjustment for a cart line's quantity. Valid values: `increase` (default), `decrease`, or `remove`. */\n adjust?: 'increase' | 'decrease' | 'remove';\n}", + }, + }, + }, + ], + }, + { + name: 'CartProvider', + category: 'components', + isVisualComponent: false, + related: [ + { + name: 'useCart', + type: 'hooks', + url: '/api/hydrogen-react/hooks/useCart', + }, + { + name: 'ShopifyProvider', + type: 'components', + url: '/api/hydrogen-react/components/shopifyprovider', + }, + ], + description: + "\n The `CartProvider` component synchronizes the state of the Storefront API Cart and a customer's cart, and allows you to more easily manipulate the cart by adding, removing, and updating it. It could be placed at the root of your app so that your whole app is able to use the `useCart()` hook anywhere.\n\nThere are props that trigger when a call to the Storefront API is made, such as `onLineAdd={}` when a line is added to the cart. There are also props that trigger when a call to the Storefront API is completed, such as `onLineAddComplete={}` when the fetch request for adding a line to the cart completes.\n\nThe `CartProvider` component must be a descendant of the `ShopifyProvider` component\n.\n ", + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: "import {CartProvider, useCart} from '@shopify/hydrogen-react';\n\nexport function App() {\n <CartProvider\n onLineAdd={() => {\n console.log('a line is being added');\n }}\n onLineAddComplete={() => {\n console.log('a line has been added');\n }}\n >\n <CartComponent />\n </CartProvider>;\n}\n\nfunction CartComponent() {\n const {linesAdd, status} = useCart();\n\n const merchandise = {merchandiseId: '{id-here}'};\n\n return (\n <div>\n Cart Status: {status}\n <button onClick={() => linesAdd([merchandise])}>Add Line</button>\n </div>\n );\n}\n", + language: 'jsx', + }, + { + title: 'TypeScript', + code: "import {CartProvider, useCart} from '@shopify/hydrogen-react';\nimport type {CartLineInput} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport function App() {\n <CartProvider\n onLineAdd={() => {\n console.log('a line is being added');\n }}\n onLineAddComplete={() => {\n console.log('a line has been added');\n }}\n >\n <CartComponent />\n </CartProvider>;\n}\n\nfunction CartComponent() {\n const {linesAdd, status} = useCart();\n\n const merchandise: CartLineInput = {merchandiseId: '{id-here}'};\n\n return (\n <div>\n Cart Status: {status}\n <button onClick={() => linesAdd([merchandise])}>Add Line</button>\n </div>\n );\n}\n", + language: 'tsx', + }, + ], + title: 'Example code', + }, + }, + definitions: [ + { + title: 'Props', + description: '', + type: 'CartProviderProps', + typeDefinitions: { + CartProviderProps: { + filePath: '/CartProvider.tsx', + syntaxKind: 'TypeAliasDeclaration', + name: 'CartProviderProps', + value: + "{\n /** Any `ReactNode` elements. */\n children: React.ReactNode;\n /** Maximum number of cart lines to fetch. Defaults to 250 cart lines. */\n numCartLines?: number;\n /** A callback that is invoked when the process to create a cart begins, but before the cart is created in the Storefront API. */\n onCreate?: () => void;\n /** A callback that is invoked when the process to add a line item to the cart begins, but before the line item is added to the Storefront API. */\n onLineAdd?: () => void;\n /** A callback that is invoked when the process to remove a line item to the cart begins, but before the line item is removed from the Storefront API. */\n onLineRemove?: () => void;\n /** A callback that is invoked when the process to update a line item in the cart begins, but before the line item is updated in the Storefront API. */\n onLineUpdate?: () => void;\n /** A callback that is invoked when the process to add or update a note in the cart begins, but before the note is added or updated in the Storefront API. */\n onNoteUpdate?: () => void;\n /** A callback that is invoked when the process to update the buyer identity begins, but before the buyer identity is updated in the Storefront API. */\n onBuyerIdentityUpdate?: () => void;\n /** A callback that is invoked when the process to update the cart attributes begins, but before the attributes are updated in the Storefront API. */\n onAttributesUpdate?: () => void;\n /** A callback that is invoked when the process to update the cart discount codes begins, but before the discount codes are updated in the Storefront API. */\n onDiscountCodesUpdate?: () => void;\n /** A callback that is invoked when the process to create a cart completes */\n onCreateComplete?: () => void;\n /** A callback that is invoked when the process to add a line item to the cart completes */\n onLineAddComplete?: () => void;\n /** A callback that is invoked when the process to remove a line item to the cart completes */\n onLineRemoveComplete?: () => void;\n /** A callback that is invoked when the process to update a line item in the cart completes */\n onLineUpdateComplete?: () => void;\n /** A callback that is invoked when the process to add or update a note in the cart completes */\n onNoteUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the buyer identity completes */\n onBuyerIdentityUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the cart attributes completes */\n onAttributesUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the cart discount codes completes */\n onDiscountCodesUpdateComplete?: () => void;\n /** An object with fields that correspond to the Storefront API's [Cart object](https://shopify.dev/api/storefront/2023-07/objects/cart). */\n data?: PartialDeep;\n /** A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/2023-07/objects/cart) for all queries and mutations. A default value is used if no argument is provided. */\n cartFragment?: string;\n /** A customer access token that's accessible on the server if there's a customer login. */\n customerAccessToken?: CartBuyerIdentityInput['customerAccessToken'];\n /** The ISO country code for i18n. */\n countryCode?: CountryCode;\n}", + description: '', + members: [ + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'children', + value: 'React.ReactNode', + description: 'Any `ReactNode` elements.', + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'numCartLines', + value: 'number', + description: + 'Maximum number of cart lines to fetch. Defaults to 250 cart lines.', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onCreate', + value: '() => void', + description: + 'A callback that is invoked when the process to create a cart begins, but before the cart is created in the Storefront API.', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onLineAdd', + value: '() => void', + description: + 'A callback that is invoked when the process to add a line item to the cart begins, but before the line item is added to the Storefront API.', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onLineRemove', + value: '() => void', + description: + 'A callback that is invoked when the process to remove a line item to the cart begins, but before the line item is removed from the Storefront API.', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onLineUpdate', + value: '() => void', + description: + 'A callback that is invoked when the process to update a line item in the cart begins, but before the line item is updated in the Storefront API.', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onNoteUpdate', + value: '() => void', + description: + 'A callback that is invoked when the process to add or update a note in the cart begins, but before the note is added or updated in the Storefront API.', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onBuyerIdentityUpdate', + value: '() => void', + description: + 'A callback that is invoked when the process to update the buyer identity begins, but before the buyer identity is updated in the Storefront API.', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onAttributesUpdate', + value: '() => void', + description: + 'A callback that is invoked when the process to update the cart attributes begins, but before the attributes are updated in the Storefront API.', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onDiscountCodesUpdate', + value: '() => void', + description: + 'A callback that is invoked when the process to update the cart discount codes begins, but before the discount codes are updated in the Storefront API.', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onCreateComplete', + value: '() => void', + description: + 'A callback that is invoked when the process to create a cart completes', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onLineAddComplete', + value: '() => void', + description: + 'A callback that is invoked when the process to add a line item to the cart completes', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onLineRemoveComplete', + value: '() => void', + description: + 'A callback that is invoked when the process to remove a line item to the cart completes', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onLineUpdateComplete', + value: '() => void', + description: + 'A callback that is invoked when the process to update a line item in the cart completes', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onNoteUpdateComplete', + value: '() => void', + description: + 'A callback that is invoked when the process to add or update a note in the cart completes', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onBuyerIdentityUpdateComplete', + value: '() => void', + description: + 'A callback that is invoked when the process to update the buyer identity completes', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onAttributesUpdateComplete', + value: '() => void', + description: + 'A callback that is invoked when the process to update the cart attributes completes', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'onDiscountCodesUpdateComplete', + value: '() => void', + description: + 'A callback that is invoked when the process to update the cart discount codes completes', + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'data', + value: 'PartialDeep', + description: + "An object with fields that correspond to the Storefront API's [Cart object](https://shopify.dev/api/storefront/2023-07/objects/cart).", + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'cartFragment', + value: 'string', + description: + "A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/2023-07/objects/cart) for all queries and mutations. A default value is used if no argument is provided.", + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'customerAccessToken', + value: 'string', + description: + "A customer access token that's accessible on the server if there's a customer login.", + isOptional: true, + }, + { + filePath: '/CartProvider.tsx', + syntaxKind: 'PropertySignature', + name: 'countryCode', + value: 'CountryCode', + description: 'The ISO country code for i18n.', + isOptional: true, + }, + ], + }, + }, + }, + ], + }, + { + name: 'ExternalVideo', + category: 'components', + isVisualComponent: false, + related: [ + { + name: 'MediaFile', + type: 'component', + url: '/api/hydrogen-react/components/mediafile', + }, + ], + description: + "The `ExternalVideo` component renders an embedded video for the Storefront API's [ExternalVideo object](https://shopify.dev/api/storefront/reference/products/externalvideo).", + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: "import {ExternalVideo} from '@shopify/hydrogen-react';\n\nexport default function MyProductVideo({products}) {\n const firstMediaElement = products.nodes[0].media.nodes[0];\n\n if (firstMediaElement.__typename === 'ExternalVideo') {\n return <ExternalVideo data={firstMediaElement} />;\n }\n}\n", + language: 'jsx', + }, + { + title: 'TypeScript', + code: "import {ExternalVideo} from '@shopify/hydrogen-react';\nimport type {ProductConnection} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport default function MyProductVideo({\n products,\n}: {\n products: ProductConnection;\n}) {\n const firstMediaElement = products.nodes[0].media.nodes[0];\n if (firstMediaElement.__typename === 'ExternalVideo') {\n return <ExternalVideo data={firstMediaElement} />;\n }\n}\n", + language: 'tsx', + }, + ], + title: 'ExternalVideo example', + }, + }, + definitions: [ + { + title: 'Props', + description: + 'Takes in the same props as a native `