diff --git a/.github/workflows/periodic-build.yml b/.github/workflows/periodic-build.yml index 0bd36f1808d..0800e4c589a 100644 --- a/.github/workflows/periodic-build.yml +++ b/.github/workflows/periodic-build.yml @@ -21,7 +21,7 @@ jobs: publish: strategy: matrix: - version: [20, 22] + version: [22, 24] runs-on: ubuntu-latest steps: - name: Checkout @@ -31,7 +31,7 @@ jobs: path: graphcommerce - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.version }} registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/pr-analysis.yml b/.github/workflows/pr-analysis.yml index 466719af485..0cf0a66cec2 100644 --- a/.github/workflows/pr-analysis.yml +++ b/.github/workflows/pr-analysis.yml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: yarn cache-dependency-path: package.json - run: yarn install diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index 14e52796823..646c36e7f0c 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -19,10 +19,20 @@ jobs: - uses: actions/setup-node@v4 with: cache: yarn - node-version: 20 + node-version: 24 cache-dependency-path: package.json registry-url: 'https://registry.npmjs.org' scope: '@graphcommerce' + - name: verify npm auth (fail fast on expired/invalid NPM_TOKEN) + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + if ! WHOAMI=$(npm whoami --registry=https://registry.npmjs.org 2>&1); then + echo "::error::NPM_TOKEN is invalid, expired, or has no publish access. npm whoami output: $WHOAMI" + echo "::error::Generate a new token at https://www.npmjs.com/settings//tokens and update the NPM_TOKEN secret." + exit 1 + fi + echo "Authenticated to npm as: $WHOAMI" - name: yarn install && yarn postinstall run: yarn install && yarn postinstall - name: set pre canary @@ -36,8 +46,12 @@ jobs: git add . git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git config user.name "github-actions[bot]" - git commit -m "chore(release): update prerelease versions" - git push + if ! git diff --staged --quiet; then + git commit -m "chore(release): update prerelease versions" + git push + else + echo "No version changes to commit; proceeding to publish step." + fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: set version variables diff --git a/.github/workflows/release-main.yml b/.github/workflows/release-main.yml index 35c2242faf0..df16945831b 100644 --- a/.github/workflows/release-main.yml +++ b/.github/workflows/release-main.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-node@v4 with: cache: yarn - node-version: 20 + node-version: 24 cache-dependency-path: package.json registry-url: 'https://registry.npmjs.org' scope: '@graphcommerce' diff --git a/.gitignore b/.gitignore index 4a19cc4f80a..173fe446382 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,7 @@ build !.yarn/sdks !.yarn/versions yarn.lock + +# playwright +test-results +playwright-report diff --git a/.gitpod.yml b/.gitpod.yml index 381dc780001..4c50ad0bd08 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,5 +1,5 @@ tasks: - - before: nvm install 18 + - before: nvm install 24 init: | cd examples/magento-graphcms yarn diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..998d23457fb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,610 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with +code in this repository. + +## Repository Overview + +GraphCommerce is a React/Next.js framework for building headless ecommerce +storefronts, backed by Magento 2 and Hygraph. It's a **Yarn 4 workspaces +monorepo** with ~68 packages. + +### graphcommerce vs graphcommerce-private + +This repo (`graphcommerce/`) is the **open-source core framework**. It is a +submodule inside the **`graphcommerce-private`** parent repo +(`../graphcommerce-private/`), which adds proprietary packages in +`packagesPrivate/` (Adobe Commerce features, B2B, gift cards, store credit, +returns, cache-notify, private pricing, etc.). + +**Where to put new code:** + +- **This repo (`graphcommerce/`)** — Generic, open-source framework features + that benefit all users. Core Magento integrations, UI components, build + tooling. +- **Parent repo (`graphcommerce-private/packagesPrivate/`)** — Specialized, + proprietary features: B2B (company accounts, purchase orders, requisition + lists, negotiable quotes), Adobe Commerce-specific features (gift cards, store + credit, returns, reward points), private pricing, cache invalidation, etc. + +When building a new feature, ask: "Is this generic enough for the open-source +framework, or is it specific to Adobe Commerce / B2B / a paid add-on?" If the +latter, it belongs in `graphcommerce-private/packagesPrivate/`. Private packages +are activated via `PRIVATE_ADDITIONAL_DEPENDENCIES` in `.env`. + +## Common Commands + +### Development + +```bash +# Start the main example storefront (runs codegen watcher + Next.js dev with Turbopack) +yarn workspace @graphcommerce/magento-graphcms dev + +# Watch + rebuild the TS-compiled framework packages in parallel +# (`pkgroll -w` / `tsc -W` — never exits, leave running during development) +yarn packages + +# One-shot build of the same set — use this before committing or +# generating patches +yarn packages:build + +# Full production build of example +yarn workspace @graphcommerce/magento-graphcms build +``` + +**After editing TS source in any of the seven framework packages that ship as +compiled `dist/`, run `yarn packages:build` before committing.** The seven +packages are: + +| Package | Path | +| ------------------------------------------------------- | --------------------------------- | +| `@graphcommerce/next-config` | `packagesDev/next-config` | +| `@graphcommerce/cli` | `packages/cli` | +| `@graphcommerce/hygraph-cli` | `packages/hygraph-cli` | +| `@graphcommerce/changeset-changelog` | `packagesDev/changeset-changelog` | +| `@graphcommerce/graphql-codegen-near-operation-file` | `packagesDev/` | +| `@graphcommerce/graphql-codegen-relay-optimizer-plugin` | `packagesDev/` | +| `@graphcommerce/graphql-codegen-markdown-docs` | `packagesDev/` | + +These ship as compiled `dist/**/*.js`, not source — without rebuilding, the +change is invisible to consumers (including local +`node_modules/@graphcommerce/*` when generating `patch-package` patches). Commit +the regenerated `dist/` alongside the source change so the patch stays in sync. +`yarn packages` (watch mode) is fine during active development — the watchers +hot-rebuild on save — but `yarn packages:build` is the one-shot pre-commit step. + +The remaining `packagesDev/*` directories (`prettier-config`, `eslint-config`, +`typescript-config`, `browserslist-config`, `misc`) are config-only — no compile +step, no rebuild needed. + +`yarn packages:build` also regenerates +`packagesDev/next-config/dist/generated/config.js` because zod-schema codegen +runs alongside `next-config`'s build. That diff is normally unrelated to your +change and should be discarded with +`git checkout -- packagesDev/next-config/dist/generated/config.js` before +committing — only keep it when your change actually touches the config schema. + +### Testing + +```bash +yarn test # Run all tests (vitest) +yarn test:watch # Watch mode +yarn test packages/foo # Run tests matching a path +``` + +Tests use Vitest with +`include: ['**/__tests__/**/*.ts', '**/*.test.ts', '**/*.test.tsx']`. + +#### Playwright (E2E) + +End-to-end tests live next to the package they exercise, named +`.playwright.ts` (e.g. +`packages/magento-product/test/youtubeEmbedInGallery.playwright.ts`, +`packages/magento-customer/test/authentication.playwright.ts`). They're +picked up by `playwright.config.ts` via `testMatch: ['**/*.playwright.ts']` +and they exercise a real running example storefront — there is no jsdom, +no mocked Apollo cache. + +One-time setup (only needed if you've never run Playwright on this machine +or the browsers are stale): + +```bash +npx playwright install chromium # or `npx playwright install` for all browsers +``` + +Day-to-day workflow: + +```bash +# Terminal A — start the storefront the test will hit +yarn workspace @graphcommerce/magento-graphcms dev + +# Terminal B — run the suite (default project is chrome, headed) +yarn playwright +# Or a single test, headless, with a non-default port / URL: +URL=http://localhost:3210 npx playwright test \ + packages/magento-product/test/youtubeEmbedInGallery.playwright.ts \ + --project=chrome --reporter=line +``` + +- `URL` overrides the `baseURL` for every test (`page.goto('/p/foo')` + resolves against `$URL/p/foo`). +- `PLAYWRIGHT_LOCALES=nl,de` generates extra `-` projects + that target `$URL/` — opt-in only because most tests are + locale-agnostic. +- `test-results/` and `playwright-report/` are gitignored — both are local + artifacts that the runner regenerates on every invocation. + +Backend assumptions: tests run against the Magento endpoint configured in +`examples/magento-graphcms/graphcommerce.config.ts`. They hardcode SKUs / +URL keys that are present in the GraphCommerce demo backend +(`configurator.reachdigital.dev`); when running against a different backend +either override via env vars (the gallery test reads `PRODUCT_URL` and +`EXPECTED_YOUTUBE_ID`) or skip backend-specific tests. + +### Linting & Type Checking + +**Important:** You can **only type check an example, never the whole repo**. +Codegen generates types per-example (`.mesh/`, `.gql.ts`) and a private/optional +package's fragments only land in the generated types of an example that +activates the package. Always run `tsgo` from inside an example directory. + +The full workflow for type checking — including any optional/private packages +you want exercised — is: + +1. **Activate the package** by adding it to `PRIVATE_ADDITIONAL_DEPENDENCIES` + in the example's `.env` (comma-separated, no spaces). Yarn workspaces also + needs the package linked, so run `yarn install` from the repo root if the + package is new or hasn't been installed before. +2. **Run codegen** so the package's `.graphql` fragments are injected into the + example's generated `.gql.ts` files: + ```bash + cd examples/magento-graphcms + yarn codegen + ``` +3. **Run `tsgo`** from the same example directory: + ```bash + npx --package=@typescript/native-preview tsgo --noEmit -p . + ``` + +Skipping steps 1–2 produces misleading "field does not exist" errors on +injected fragments, because the example's types still reflect the previously +activated set of packages. Running `tsgo` from the repo root fails for the same +reason — there is no single project-wide type-check, only per-example checks. + +```bash +yarn eslint:lint # ESLint across all TS/TSX +yarn eslint:fix # ESLint with auto-fix +yarn prettier:fix # Prettier formatting +``` + +### Code Generation + +```bash +# In an example directory — regenerate GraphQL Mesh + codegen types +yarn workspace @graphcommerce/magento-graphcms codegen + +# Individual steps: +graphcommerce codegen # Generate config types +gc-mesh build # Build GraphQL Mesh (merges Magento + Hygraph schemas) +gc-gql-codegen # Generate TypeScript from .graphql files → .gql.ts +``` + +### Cleaning Up Codegen Interceptors + +Codegen wraps any module that has a matching plugin with an interceptor +(`*.tsx` rewritten to a re-export, original saved as `*.original.tsx`). When +you change `PRIVATE_ADDITIONAL_DEPENDENCIES`, switch branches, or are about to +commit, those wrappers should not end up in `git status`. Restore the +originals with: + +```bash +# Run from any example directory (e.g. examples/magento-graphcms) +graphcommerce cleanup-interceptors +``` + +The command walks every `*.original.*` it finds and restores it back over the +matching wrapped file. **Always run this before committing**, otherwise you +end up with diffs to dozens of `next-ui`, `magento-cart-items`, +`magento-customer`, etc. files that only exist because of your local +`.env`/plugin set. + +### Showcasing New Features + +**Every feature PR must wire the feature into one of the existing example +storefronts so it is visible and presentable on a real page.** A standalone +demo route under `pages/test/*` is **not** a showcase — it lives outside the +real flow, never gets walked by a reviewer, and proves nothing about the +integration. The bar is "if I `yarn workspace @graphcommerce/magento-graphcms +dev` and open the storefront, can I navigate to a place where this feature is +actually used?". + +Concrete patterns by feature kind: + +- **A new UI component** — find the existing page in `examples/magento-graphcms` + (or `examples/magento-open-source`) where it belongs and render it there. A + product video player gets wired into the product page's media gallery, a new + account-menu element into the account layout, a new cart summary block into + the cart page, etc. If the showcase needs backend content (a product with a + YouTube video uploaded, a Magento config flipped on, a Hygraph row created), + call that out in the PR description so the reviewer knows what to configure + before previewing. +- **A new plugin** — wire it where it would actually wrap something, and make + sure the storefront still renders. Activating the package via + `PRIVATE_ADDITIONAL_DEPENDENCIES` is enough wiring as long as the host + component is reachable from a real page in the example. +- **A new GraphQL fragment / mutation** — exercise it from the page or + component that needs the data; don't ship the fragment alone. + +The PR description's "Test plan" must name the storefront URL (or click path) +the reviewer should open to see the feature working. + +### i18n + +```bash +yarn workspace @graphcommerce/magento-graphcms lingui # Extract translation strings +``` + +### Changesets & Versioning + +Every PR needs a `.changeset/.md` file — the changesets bot otherwise +blocks the PR with `⚠️ No Changeset found`. Frontmatter lists the affected +packages and the bump type, followed by a short description: + +```md +--- +'@graphcommerce/magento-customer': minor +'@graphcommerce/magento-store': patch +--- + +Short, complete-sentence description of what changed and why. +``` + +**Bump types — almost always `patch` or `minor`:** + +- `patch` — bug fixes, internal refactors with no API change, doc tweaks, dist + regenerations. Most PRs. +- `minor` — additive changes: new components, new optional props, new config + options, new exports. Default for new features. +- `major` — **avoid almost always.** A major release ships with downstream + migration cost (consumer projects rewrite code), an upgrade guide, and + coordinated marketing/release communication. Reserve for genuinely breaking + changes that can't be expressed additively, and coordinate with the + maintainers before opening the PR. When the change looks breaking, first ask: + can it be a new optional prop / new export that defaults to the old behavior? + If yes, that's a `minor`. + +**Placeless changes go in `@graphcommerce/misc`.** For docs-only PRs, repo-wide +tooling, root-level scripts, CLAUDE.md updates, GitHub workflows — anything that +doesn't logically bump a published package — target `@graphcommerce/misc` as +`patch`: + +```md +--- +'@graphcommerce/misc': patch +--- + +Document . +``` + +`@graphcommerce/misc` (`packagesDev/misc/`) is intentionally empty — it has no +consumers, so the published changelog stays clean. + +**Stuck release recovery hint:** when a canary publish fails after versions are +already bumped, remove the not-yet-published changeset IDs from +`.changeset/pre.json`'s `changesets` array and push — the next run will +re-consume them and retry the publish. + +## Architecture + +### Directory Layout + +- **`packages/`** — Core feature packages (`@graphcommerce/*`): UI components, + Magento integrations (cart, checkout, product, search, payment), Hygraph/CMS, + analytics, Algolia search +- **`packagesDev/`** — Build tooling: `next-config` (the framework's core build + system), codegen plugins, ESLint/Prettier/TypeScript configs, CLI +- **`examples/`** — Full storefronts: `magento-graphcms` (primary), + `magento-open-source` +- **`docs/`** — Documentation site + +### GraphQL Mesh (Schema Federation) + +Multiple GraphQL/REST sources are combined into a single schema via GraphQL +Mesh. The merged schema is generated into `examples/*/.mesh/index.ts`. Sources +include Magento GraphQL and Hygraph, with transforms for field renaming, +filtering, and type merging. + +### GraphQL Codegen + +`.graphql` files throughout packages generate co-located `.gql.ts` files +containing TypeScript types and Apollo Client hooks. The **fragment injection +system** uses an `@inject` directive to extend existing queries: + +```graphql +fragment MyCustomData on ProductInterface @inject(into: ["ProductListItem"]) { + my_custom_attribute +} +``` + +### Plugin System (Zero-Runtime Interception) + +Plugins live in `plugins/` directories and are auto-discovered at build time. +They work via webpack/Turbopack interceptors — no runtime cost. Three types: + +- **`component`** — Wraps a React component. Receives `Prev` as a prop to render + the original. +- **`function`** — Intercepts a function call. Receives `prev` to call the + original. +- **`replace`** — Completely replaces an export. + +Each plugin exports a `config: PluginConfig` with `type` and `module` (the +`@graphcommerce/*` package to intercept). Conditional plugins use `ifConfig`. +Restart dev server after adding the first plugin; hot-reload works after that. + +### Configuration + +`graphcommerce.config.ts` in each example defines storefront config (Magento +endpoint, Hygraph endpoint, locales, feature flags). Required fields: +`canonicalBaseUrl`, `magentoEndpoint`, `magentoVersion` (245/246/247), +`hygraphEndpoint`, `storefront[]` (locale + magentoStoreCode). + +Access config values in code: + +```tsx +import { cartDisplayPricesInclTax } from '@graphcommerce/next-config/config' +import { useStorefrontConfig } from '@graphcommerce/next-ui' + +// Global value +const global = cartDisplayPricesInclTax + +// Per-storefront with fallback +const scoped = + useStorefrontConfig().cartDisplayPricesInclTax ?? cartDisplayPricesInclTax +``` + +Extend the config schema by creating a `graphql/Config.graphqls` file: + +```graphql +extend input GraphCommerceConfig { + myConfig: String +} +extend input GraphCommerceStorefrontConfig { + myField: Boolean +} +``` + +You can also extend existing enums from other packages: + +```graphql +extend enum WebsitePermissions { + CUSTOMER_ONLY +} +``` + +### Environment Variables (.env) + +Each example directory has a `.env` file. Two types of env vars are used: + +**`GC_*` — GraphCommerce config overrides.** Any config value can be overridden: + +- Convert camelCase to SCREAMING*SNAKE_CASE, prefix with `GC*` +- Arrays indexed with `_0`, `_1`: `GC_STOREFRONT_0_LOCALE="en"` +- Nested objects with `_`: `GC_DEBUG_PLUGIN_STATUS="1"` +- Booleans: `"1"` / `"true"` / `"0"` / `"false"` +- Objects as JSON: `GC_PERMISSIONS='{"cart":"CUSTOMER_ONLY"}'` + +Examples: + +```bash +GC_MAGENTO_ENDPOINT="https://magento.example.com/graphql" +GC_MAGENTO_VERSION="247" +GC_LIMIT_SSG="1" +GC_PREVIEW_SECRET="mySecret" +GC_GOOGLE_RECAPTCHA_KEY="6Ld..." +GC_GRAPHQL_MESH_EDIT_MODE="1" +``` + +Export current config to env vars: `yarn graphcommerce export-config` + +**`PRIVATE_ADDITIONAL_DEPENDENCIES` — Activate optional packages.** +Comma-separated list of `@graphcommerce/*` packages to include in dependency +resolution without adding them to `package.json`. This enables their plugins, +GraphQL fragments, and mesh config extensions: + +```bash +PRIVATE_ADDITIONAL_DEPENDENCIES="@graphcommerce/magento-search-overlay,@graphcommerce/b2b-permissions" +``` + +This is also used in the root `package.json` test scripts to include optional +packages during testing. The resolution system only picks up packages with +`graphcommerce` in the name (configurable via `PRIVATE_PACKAGE_NAMESPACES`). + +## Plugin System (Development Guide) + +**When building new features, prefer plugins over direct code modifications.** +Plugins keep functionality modular and independent from the core codebase. + +### Creating a Plugin + +Place files in `plugins/` directory (in your example project or in a package). +Each plugin file exports a `config` object and the named export to intercept. + +**Component plugin** — wrap a React component: + +```tsx +import type { ProductListItemProps } from '@graphcommerce/magento-product' +import type { PluginConfig, PluginProps } from '@graphcommerce/next-config' + +export const config: PluginConfig = { + type: 'component', + module: '@graphcommerce/magento-product', +} + +export function ProductListItem(props: PluginProps) { + const { Prev, ...rest } = props + return +} +``` + +**Function plugin** — intercept a function: + +```tsx +import type { graphqlConfig as graphqlConfigType } from '@graphcommerce/graphql' +import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config' + +export const config: PluginConfig = { + type: 'function', + module: '@graphcommerce/graphql', +} + +export const graphqlConfig: FunctionPlugin = ( + prev, + conf, +) => { + const results = prev(conf) + return { ...results, links: [...results.links, myCustomLink] } +} +``` + +**Replacement plugin** — completely replace an export (no access to original): + +```tsx +export const config: PluginConfig = { + type: 'replace', + module: '@graphcommerce/magento-product', +} + +export function ProductListCount(props: ProductCountProps) { + return
{props.total_count}
+} +``` + +### Plugin Rules + +- The exported function/component name must match the target export exactly +- Restart dev server after creating the first plugin; hot-reload works after + that +- Plugin loading order follows `package.json` dependency order; local plugins + wrap last (closest to the original) +- Auto-discovered via glob: `${packageLocation}/plugins/**/*.{ts,tsx}` +- Only packages with `graphcommerce` in the name are scanned + +### Conditional Plugins + +Load a plugin only when a config value is truthy or matches a specific value: + +```tsx +export const config: PluginConfig = { + type: 'component', + module: '@graphcommerce/magento-product', + ifConfig: 'demoMode', // loads when demoMode is truthy +} + +// Or match a specific value: +export const config: PluginConfig<'compareVariant'> = { + type: 'component', + module: '@graphcommerce/magento-product', + ifConfig: ['compareVariant', 'CHECKBOX'], +} +``` + +### Extending GraphQL Queries via Fragment Injection + +Instead of modifying queries directly, inject fields into existing fragments: + +```graphql +fragment MyCustomFragment on ProductInterface +@inject(into: ["ProductListItem"]) { + my_custom_attribute +} +``` + +After running `yarn codegen`, `my_custom_attribute` will be available in +`ProductListItem.gql.ts` and all queries that use that fragment. + +### Debugging Plugins + +Enable with `debug.pluginStatus: true` in config or `GC_DEBUG_PLUGIN_STATUS=1` +in env. This logs which plugins are enabled/disabled during build. + +## Authentication & Cookies + +Customer auth tokens are stored in Apollo Client cache. Auth state is tracked +via the CSS flags system: the `private-query` flag in the `gc-flags` cookie +indicates a logged-in user. This is set automatically by `setCssFlag` on sign-in +and cleared by `removeCssFlag` on sign-out. The cookie is readable both +client-side and server-side (in Next.js proxy). + +**Cookie utility** (`@graphcommerce/next-ui`): + +```tsx +import { cookie } from '@graphcommerce/next-ui' +cookie('name', 'value') // set +cookie('name') // read +cookie('name', null) // delete +``` + +### CSS Flags + +CSS flags set `data-*` attributes on `` for instant visual toggling before +JS hydrates. Stored in a `gc-flags` cookie (JSON), restored via a blocking +script in `_document.tsx` (`getCssFlagsInitScript()`) that reads +`document.cookie`. The cookie is also readable server-side in Next.js proxy. + +```tsx +import { + setCssFlag, + removeCssFlag, + cssFlag, + cssNotFlag, +} from '@graphcommerce/next-ui' + +setCssFlag('private-query', true) +// In MUI sx: { [cssFlag('private-query')]: { display: 'none' } } +// In MUI sx: { [cssNotFlag('private-query')]: { display: 'none' } } +``` + +The `private-query` flag is set on sign-in, cleared on sign-out. Used by +`GuestOrCustomerMask` and `PrivateQueryMask` to show/hide customer-specific +content without waiting for React hydration. + +### Permissions System + +Permissions are configured via `GraphCommercePermissions` input type in +`Config.graphqls`. Set globally and overridable per storefront: + +```tsx +import { permissions } from '@graphcommerce/next-config/config' +import { useStorefrontConfig } from '@graphcommerce/next-ui' +const perm = + useStorefrontConfig().permissions?.website ?? + permissions?.website ?? + 'ENABLED' +``` + +Existing enums: `WebsitePermissions`, `CustomerAccountPermissions`, +`CartPermissions`, `BillingAddressPermissions`. Extendable via `extend enum` in +package `Config.graphqls` files. + +### GuestOrCustomerMask + +Component for showing different content based on auth state, with CSS-flag-based +masking to avoid flash of wrong content: + +```tsx +} + skeleton={} + loggedIn={} +/> +``` + +## Tech Stack + +- Next.js with Turbopack, React 19, TypeScript 5.9 +- Apollo Client 4 for GraphQL +- MUI (Material-UI) + Emotion for styling +- Lingui for i18n +- Framer Motion for animations +- React Hook Form for forms +- Changesets for versioning/releases diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3759b5ef1e7..573d2f7a2ae 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,99 @@ # Change Log +## 11.0.0 + +## 11.0.0-canary.45 + +## 10.1.0-canary.44 + +## 10.1.0-canary.43 + +## 10.1.0-canary.42 + +## 10.1.0-canary.41 + +## 10.1.0-canary.40 + +## 10.1.0-canary.39 + +## 10.1.0-canary.38 + +## 10.1.0-canary.37 + +## 10.1.0-canary.36 + +## 10.1.0-canary.35 + +## 10.1.0-canary.34 + +## 10.1.0-canary.33 + +## 10.1.0-canary.32 + +## 10.1.0-canary.31 + +## 10.1.0-canary.30 + +## 10.1.0-canary.29 + +## 10.1.0-canary.28 + +## 10.1.0-canary.27 + +## 10.1.0-canary.26 + +## 10.1.0-canary.25 + +## 10.1.0-canary.24 + +## 10.1.0-canary.23 + +## 10.1.0-canary.22 + +## 10.1.0-canary.21 + +## 10.1.0-canary.20 + +## 10.1.0-canary.19 + +## 10.1.0-canary.18 + +## 10.1.0-canary.17 + +## 10.1.0-canary.16 + +## 10.1.0-canary.15 + +## 10.1.0-canary.14 + +## 10.1.0-canary.13 + +## 10.1.0-canary.12 + +## 10.1.0-canary.11 + +## 10.1.0-canary.10 + +## 10.1.0-canary.9 + +## 10.1.0-canary.8 + +## 10.1.0-canary.7 + +## 10.1.0-canary.6 + +## 10.1.0-canary.5 + +## 10.1.0-canary.4 + +## 10.1.0-canary.3 + +## 10.0.4-canary.2 + +## 10.0.4-canary.1 + +## 10.0.4-canary.0 + ## 10.0.3 ## 10.0.3-canary.0 diff --git a/docs/framework/mesh.md b/docs/framework/mesh.md index 082ecbb913e..1a9654a47b9 100644 --- a/docs/framework/mesh.md +++ b/docs/framework/mesh.md @@ -92,3 +92,35 @@ To make sure changes are picked up during development set the config value `graphqlMeshEditMode: true` in your graphcommerce.config.js or set the env variable `GC_GRAPHQL_MESH_EDIT_MODE=1`. This _will_ make the frontend considerably slower. + +## Routing Magento traffic over an internal network + +When the frontend runs next to Magento — e.g. both in the same Kubernetes +cluster — the mesh's server-side Magento requests (GraphQL and REST) can be +routed directly to an internal Service instead of hairpinning over the public +load balancer. Set in the **runtime** environment (e.g. a Kubernetes +ConfigMap): + +``` +GC_MAGENTO_ENDPOINT_SERVER=http://varnish.magento-namespace.svc.cluster.local +``` + +Every request whose URL starts with the origin of `GC_MAGENTO_ENDPOINT` is +rewritten to this origin, so both the GraphQL endpoint and the REST endpoint +are covered. `GC_MAGENTO_ENDPOINT` itself stays the public URL: it keeps +feeding build-time schema introspection, `images.remotePatterns` and the media +URLs Magento generates. The rewrite also sends `X-Forwarded-Proto: https`, +which the TLS-terminating proxy would normally add — Magento needs it to keep +generating `https://` URLs. + +Caveats: + +- Both `GC_MAGENTO_ENDPOINT_SERVER` and `GC_MAGENTO_ENDPOINT` must be present + in the runtime environment; the rewrite happens per request at runtime. +- Do **not** set `GC_MAGENTO_ENDPOINT_SERVER` in the build environment: schema + introspection (`gc-mesh build`) and static generation run where the internal + endpoint is typically not reachable. Unset, the feature is a no-op. +- Point it at the Varnish service (not the webserver directly) to keep + Magento's GraphQL full-page cache in the path. +- When a NetworkPolicy guards the Magento namespace, allow ingress from the + frontend namespace to the Varnish pods. diff --git a/docs/getting-started/create.md b/docs/getting-started/create.md index e8e24227fb5..db1daf209d0 100644 --- a/docs/getting-started/create.md +++ b/docs/getting-started/create.md @@ -11,7 +11,7 @@ start building. ### Preparations - MacOS, Windows with WSL2 or Linux -- Install and use node 20: `nvm install 20` or `nvm use 20` +- Install and use node 22 or 24: `nvm install 24` or `nvm use 24` - Install yarn: `corepack enable` ## Step 1: Create a GraphCommerce app diff --git a/docs/package.json b/docs/package.json index 2ab2fe86e60..b9be2e92442 100644 --- a/docs/package.json +++ b/docs/package.json @@ -2,10 +2,10 @@ "name": "@graphcommerce/docs", "homepage": "https://www.graphcommerce.org/docs", "repository": "github:graphcommerce-org/graphcommerce/docs", - "version": "10.0.3", + "version": "11.0.0", "sideEffects": true, "peerDependencies": { - "@graphcommerce/prettier-config-pwa": "^10.0.3" + "@graphcommerce/prettier-config-pwa": "^11.0.0" }, "prettier": "@graphcommerce/prettier-config-pwa" } diff --git a/docs/upgrading/graphcommerce-10-to-11.md b/docs/upgrading/graphcommerce-10-to-11.md new file mode 100644 index 00000000000..55b7348e14f --- /dev/null +++ b/docs/upgrading/graphcommerce-10-to-11.md @@ -0,0 +1,94 @@ +# Upgrading from GraphCommerce 10 to 11 + +GraphCommerce 11 drops support for Node.js 20 (EOL April 2026) and aligns the +framework with the +[Node.js release schedule](https://nodejs.org/en/about/previous-releases). The +minimum supported version is now Node.js 22, and Node.js 24 is recommended. + +There are no source-code or API breaking changes in this release — only the +supported runtime has shifted. + +## Step 1: Upgrade your local Node.js + +| Version | Status as of May 2026 | +| -------------------- | ----------------------------------------------------- | +| Node.js 20 (Iron) | **EOL** — no longer supported | +| Node.js 22 (Jod) | Maintenance LTS through ~April 2027 — **new minimum** | +| Node.js 24 (Krypton) | Active LTS since Oct 2025 — **recommended** | +| Node.js 26 | Current since May 2026 — supported | + +Pick whichever LTS you prefer; `>=22.0.0 <27.0.0` is the accepted range. + +With `nvm`: + +```bash +nvm install 24 +nvm use 24 +``` + +Then re-enable corepack so the project's pinned yarn version is picked up: + +```bash +corepack enable +``` + +## Step 2: Bump `engines.node` in your project + +If you copied the example's `engines.node` into your own `package.json`, update +it from the old GC 10 value to the GC 11 range: + +```diff + "engines": { +- "node": ">=20 <24.0.0" ++ "node": ">=22.0.0 <27.0.0" + } +``` + +If your project doesn't set `engines.node` itself, nothing to do — the engine +constraint is inherited from `@graphcommerce/magento-graphcms` (or whichever +example you bootstrapped from). + +## Step 3: Update your CI / hosting + +Anywhere that pins a Node version, move from 20 to 22 or 24: + +- **GitHub Actions** (`.github/workflows/*.yml`): + + ```diff + - uses: actions/setup-node@v4 + with: + - node-version: 20 + + node-version: 24 + ``` + + If you use `actions/setup-node@v3`, bump it to `@v4` while you're there. + +- **Vercel** — set the project's "Node.js Version" to 22.x or 24.x under + Settings → General. Vercel's `20.x` option will be removed in line with Node's + own EOL. + +- **Docker images** — update any `FROM node:20…` to `FROM node:22-…` or + `FROM node:24-…` (alpine/slim/bookworm variants all work). + +- **Devcontainer / Gitpod / Codespaces** — Gitpod users can switch their + `.gitpod.yml` bootstrap from `nvm install 18` (or 20) to `nvm install 24`. + Devcontainer setups that use `"lts": true` resolve to v24 automatically and + need no change. + +## Step 4: Reinstall + +After upgrading Node: + +```bash +rm -rf node_modules .next +yarn install +yarn codegen +yarn dev +``` + +## Notes + +- Node 22 will move from Maintenance LTS to EOL around April 2027 — start + planning the move to Node 24 (or 26) before then. +- Node 25/26 are non-LTS "current" releases. They're accepted by the engine + range, but production deployments should generally stay on the latest LTS. diff --git a/docs/upgrading/readme.md b/docs/upgrading/readme.md index d363c1c2ab1..480e77596cc 100644 --- a/docs/upgrading/readme.md +++ b/docs/upgrading/readme.md @@ -110,6 +110,7 @@ After resolving the diff issues, manually process upgrade instructions: - [Upgrading to GraphCommerce 7 to 8](../upgrading/graphcommerce-7-to-8.md) - [Upgrading to GraphCommerce 8 to 9](../upgrading/graphcommerce-8-to-9.md) - [Upgrading to GraphCommerce 9 to 10](../upgrading/graphcommerce-9-to-10.md) +- [Upgrading to GraphCommerce 10 to 11](../upgrading/graphcommerce-10-to-11.md) Run and validate your local environment: diff --git a/examples/magento-graphcms/.gitignore b/examples/magento-graphcms/.gitignore index 7ff2e15df4d..5ed613d72b1 100644 --- a/examples/magento-graphcms/.gitignore +++ b/examples/magento-graphcms/.gitignore @@ -38,6 +38,7 @@ pages/account/payment-tokens.tsx pages/downloadable/download/link/id/\[id\].tsx pages/downloadable/download/linkSample/link_id/\[id\].tsx pages/downloadable/download/sample/sample_id/\[id\].tsx +pages/private.tsx pages/test/\[\[...url\]\].tsx pages/test/buttons.tsx pages/test/form-elements.tsx @@ -48,4 +49,5 @@ pages/test/sheet.tsx pages/test/slider.tsx pages/test/typography.tsx pages/test/usebacklink/\[\[...url\]\].tsx +proxy.ts # end managed by: graphcommerce diff --git a/examples/magento-graphcms/CHANGELOG.md b/examples/magento-graphcms/CHANGELOG.md index f0b4f49677e..a64a57765f7 100644 --- a/examples/magento-graphcms/CHANGELOG.md +++ b/examples/magento-graphcms/CHANGELOG.md @@ -1,5 +1,155 @@ # Change Log +## 11.0.0 + +### Major Changes + +- [#2626](https://github.com/graphcommerce-org/graphcommerce/pull/2626) [`e2c9f9b`](https://github.com/graphcommerce-org/graphcommerce/commit/e2c9f9bfc762ed1c4d46f1e24babd6bd658c7343) - **Breaking change — requires Node.js 22 or newer.** This kicks off GraphCommerce 11. + + Node.js 20 hit end-of-life in April 2026, so the framework no longer supports it. `engines.node` is now `>=22.0.0 <27.0.0` across the root and all example storefronts — Node 22 (Maintenance LTS) is the new minimum, Node 24 (Active LTS) is recommended, and Node 26 (current) is also accepted. + + - CI: `release-canary`, `release-main` and `pr-analysis` now run on Node 24. `periodic-build` matrix changed from `[20, 22]` to `[22, 24]`, and `actions/setup-node` bumped from v3 to v4. + - `.gitpod.yml` bootstrap moved from `nvm install 18` to `nvm install 24`. + - Getting-started docs and the three example READMEs now point at Node 22/24. + + See [docs/upgrading/graphcommerce-10-to-11.md](docs/upgrading/graphcommerce-10-to-11.md) for the migration steps. ([@paales](https://github.com/paales)) + +### Minor Changes + +- [#2637](https://github.com/graphcommerce-org/graphcommerce/pull/2637) [`bdaa6ec`](https://github.com/graphcommerce-org/graphcommerce/commit/bdaa6ec6aa2669b74fc6702ad46336db4c154b7c) - Refactored `LayoutNavigation` into composable pieces (`Header`, `HeaderContainer`, `MenuOverlay`, project-local `LayoutDefault`). `LayoutDefault` / `LayoutDefaultProps` in `@graphcommerce/next-ui` are marked `@deprecated`. If you are upgrading and do not want these changes, you can just discard them. This is just a structural change for more ease of use. No visually change. ([@bramvanderholst](https://github.com/bramvanderholst)) + +### Patch Changes + +- [#2634](https://github.com/graphcommerce-org/graphcommerce/pull/2634) [`06082ad`](https://github.com/graphcommerce-org/graphcommerce/commit/06082ad47a59217fbdfb24f1a60411e4d0eecd9b) - Make GraphCommerce compatible with Apollo Client 4.2+ by augmenting Apollo's `DefaultOptions` type with the `preview` extension and the SSR clients' `errorPolicy: 'all'` default. ([@bramvanderholst](https://github.com/bramvanderholst)) + +- [#2623](https://github.com/graphcommerce-org/graphcommerce/pull/2623) [`241576a`](https://github.com/graphcommerce-org/graphcommerce/commit/241576a981fe97595000d74630a8ac8544a5e77f) - Bump Next.js from 16.1.1 to 16.2.6 across the framework and example storefronts. Also bumps the matching `@next/env` and `@next/eslint-plugin-next` pins. ([@paales](https://github.com/paales)) + +- [#2636](https://github.com/graphcommerce-org/graphcommerce/pull/2636) [`0af6794`](https://github.com/graphcommerce-org/graphcommerce/commit/0af679485950c934cdadc56b8b3ea8ace9de8ab9) - Fix for codegen issue where codegen would stop (without error) halfway through ([@bramvanderholst](https://github.com/bramvanderholst)) + +## 11.0.0-canary.45 + +### Major Changes + +- [#2626](https://github.com/graphcommerce-org/graphcommerce/pull/2626) [`e2c9f9b`](https://github.com/graphcommerce-org/graphcommerce/commit/e2c9f9bfc762ed1c4d46f1e24babd6bd658c7343) - **Breaking change — requires Node.js 22 or newer.** This kicks off GraphCommerce 11. + + Node.js 20 hit end-of-life in April 2026, so the framework no longer supports it. `engines.node` is now `>=22.0.0 <27.0.0` across the root and all example storefronts — Node 22 (Maintenance LTS) is the new minimum, Node 24 (Active LTS) is recommended, and Node 26 (current) is also accepted. + + - CI: `release-canary`, `release-main` and `pr-analysis` now run on Node 24. `periodic-build` matrix changed from `[20, 22]` to `[22, 24]`, and `actions/setup-node` bumped from v3 to v4. + - `.gitpod.yml` bootstrap moved from `nvm install 18` to `nvm install 24`. + - Getting-started docs and the three example READMEs now point at Node 22/24. + + See [docs/upgrading/graphcommerce-10-to-11.md](docs/upgrading/graphcommerce-10-to-11.md) for the migration steps. ([@paales](https://github.com/paales)) + +## 10.1.0-canary.44 + +## 10.1.0-canary.43 + +## 10.1.0-canary.42 + +## 10.1.0-canary.41 + +## 10.1.0-canary.40 + +## 10.1.0-canary.39 + +## 10.1.0-canary.38 + +## 10.1.0-canary.37 + +## 10.1.0-canary.36 + +## 10.1.0-canary.35 + +## 10.1.0-canary.34 + +## 10.1.0-canary.33 + +## 10.1.0-canary.32 + +## 10.1.0-canary.31 + +## 10.1.0-canary.30 + +## 10.1.0-canary.29 + +## 10.1.0-canary.28 + +## 10.1.0-canary.27 + +## 10.1.0-canary.26 + +## 10.1.0-canary.25 + +### Minor Changes + +- [#2637](https://github.com/graphcommerce-org/graphcommerce/pull/2637) [`bdaa6ec`](https://github.com/graphcommerce-org/graphcommerce/commit/bdaa6ec6aa2669b74fc6702ad46336db4c154b7c) - Refactored `LayoutNavigation` into composable pieces (`Header`, `HeaderContainer`, `MenuOverlay`, project-local `LayoutDefault`). `LayoutDefault` / `LayoutDefaultProps` in `@graphcommerce/next-ui` are marked `@deprecated`. If you are upgrading and do not want these changes, you can just discard them. This is just a structural change for more ease of use. No visually change. ([@bramvanderholst](https://github.com/bramvanderholst)) + +## 10.1.0-canary.24 + +### Patch Changes + +- [#2636](https://github.com/graphcommerce-org/graphcommerce/pull/2636) [`0af6794`](https://github.com/graphcommerce-org/graphcommerce/commit/0af679485950c934cdadc56b8b3ea8ace9de8ab9) - Fix for codegen issue where codegen would stop (without error) halfway through ([@bramvanderholst](https://github.com/bramvanderholst)) + +## 10.1.0-canary.23 + +### Patch Changes + +- [#2634](https://github.com/graphcommerce-org/graphcommerce/pull/2634) [`06082ad`](https://github.com/graphcommerce-org/graphcommerce/commit/06082ad47a59217fbdfb24f1a60411e4d0eecd9b) - Make GraphCommerce compatible with Apollo Client 4.2+ by augmenting Apollo's `DefaultOptions` type with the `preview` extension and the SSR clients' `errorPolicy: 'all'` default. ([@bramvanderholst](https://github.com/bramvanderholst)) + +## 10.1.0-canary.22 + +## 10.1.0-canary.21 + +## 10.1.0-canary.20 + +## 10.1.0-canary.19 + +## 10.1.0-canary.18 + +## 10.1.0-canary.17 + +### Patch Changes + +- [#2623](https://github.com/graphcommerce-org/graphcommerce/pull/2623) [`241576a`](https://github.com/graphcommerce-org/graphcommerce/commit/241576a981fe97595000d74630a8ac8544a5e77f) - Bump Next.js from 16.1.1 to 16.2.6 across the framework and example storefronts. Also bumps the matching `@next/env` and `@next/eslint-plugin-next` pins. ([@paales](https://github.com/paales)) + +## 10.1.0-canary.16 + +## 10.1.0-canary.15 + +### Patch Changes + +- [#2623](https://github.com/graphcommerce-org/graphcommerce/pull/2623) [`241576a`](https://github.com/graphcommerce-org/graphcommerce/commit/241576a981fe97595000d74630a8ac8544a5e77f) - Bump Next.js from 16.1.1 to 16.2.6 across the framework and example storefronts. Also bumps the matching `@next/env` and `@next/eslint-plugin-next` pins. ([@paales](https://github.com/paales)) + +## 10.1.0-canary.14 + +## 10.1.0-canary.13 + +## 10.1.0-canary.12 + +## 10.1.0-canary.11 + +## 10.1.0-canary.10 + +## 10.1.0-canary.9 + +## 10.1.0-canary.8 + +## 10.1.0-canary.7 + +## 10.1.0-canary.6 + +## 10.1.0-canary.5 + +## 10.1.0-canary.4 + +## 10.1.0-canary.3 + +## 10.0.4-canary.2 + +## 10.0.4-canary.1 + +## 10.0.4-canary.0 + ## 10.0.3 ## 10.0.3-canary.0 diff --git a/examples/magento-graphcms/README.md b/examples/magento-graphcms/README.md index 865df4e6dfe..9c0f31407d0 100644 --- a/examples/magento-graphcms/README.md +++ b/examples/magento-graphcms/README.md @@ -52,7 +52,7 @@ start building. ### Requirements -- Install and use node 16/18: `nvm install 16` or `nvm use 16` +- Install and use node 22 or 24: `nvm install 24` or `nvm use 24` - Install yarn: `corepack enable` ## Step 1: Create a GraphCommerce app diff --git a/examples/magento-graphcms/components/GraphCMS/RowHeroBanner/RowHeroBanner.tsx b/examples/magento-graphcms/components/GraphCMS/RowHeroBanner/RowHeroBanner.tsx index 1f531bd47f5..cd9c6e13dcf 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowHeroBanner/RowHeroBanner.tsx +++ b/examples/magento-graphcms/components/GraphCMS/RowHeroBanner/RowHeroBanner.tsx @@ -1,10 +1,19 @@ -import { RichText } from '@graphcommerce/hygraph-ui' +import type { AssetProps } from '@graphcommerce/hygraph-ui' +import { Asset, RichText } from '@graphcommerce/hygraph-ui' import { breakpointVal, HeroBanner } from '@graphcommerce/next-ui' import { Button } from '@mui/material' import { RowHeroBannerFragment } from './RowHeroBanner.gql' -export function RowHeroBanner(props: RowHeroBannerFragment) { - const { copy, heroAsset, pageLinks } = props +export type RowHeroBannerProps = RowHeroBannerFragment & { + /** + * Only applies to an image asset — a video is never lazy-loaded. Pass + * `'eager'` where the banner is the LCP element. + */ + loading?: AssetProps['loading'] +} + +export function RowHeroBanner(props: RowHeroBannerProps) { + const { copy, heroAsset, pageLinks, loading } = props return ( ))} - videoSrc={heroAsset.url} + asset={heroAsset && } sx={(theme) => ({ '& .HeroBanner-copy': { minHeight: { xs: 'min(70vh,600px)', md: 'min(70vh,1080px)' }, diff --git a/examples/magento-graphcms/components/Layout/Header.tsx b/examples/magento-graphcms/components/Layout/Header.tsx new file mode 100644 index 00000000000..9d9886d6232 --- /dev/null +++ b/examples/magento-graphcms/components/Layout/Header.tsx @@ -0,0 +1,82 @@ +import { useCartEnabled } from '@graphcommerce/magento-cart' +import { CustomerFab } from '@graphcommerce/magento-customer' +import { SearchFab, SearchField } from '@graphcommerce/magento-search' +import { StoreSwitcherButton, StoreSwitcherFab } from '@graphcommerce/magento-store' +import { WishlistFab } from '@graphcommerce/magento-wishlist' +import { + DesktopNavActions, + DesktopNavBar, + DesktopNavItem, + iconChevronDown, + iconCustomerService, + iconHeart, + IconSvg, + MobileTopRight, + PlaceholderFab, + type UseNavigationSelection, +} from '@graphcommerce/next-ui' +import { t } from '@lingui/core/macro' +import { Trans } from '@lingui/react/macro' +import { Fab } from '@mui/material' +import { productListRenderer } from '../ProductListItems/productListRenderer' +import { HeaderContainer } from './HeaderContainer' +import type { LayoutQuery } from './Layout.gql' +import { Logo } from './Logo' + +export type HeaderProps = LayoutQuery & { selection: UseNavigationSelection } + +export function Header(props: HeaderProps) { + const { menu, selection } = props + const cartEnabled = useCartEnabled() + + return ( + + + + + {menu?.items?.[0]?.children?.slice(0, 2).map((item) => ( + + {item?.name} + + ))} + + selection.set([menu?.items?.[0]?.uid || ''])} + onKeyUp={(evt) => { + if (evt.key === 'Enter') { + selection.set([menu?.items?.[0]?.uid || '']) + } + }} + tabIndex={0} + > + {menu?.items?.[0]?.name} + + + + + Blog + + + + + + + + + + } /> + + {/* The placeholder exists because the CartFab is sticky but we want to reserve the space for the */} + {cartEnabled && } + + + + + + + + ) +} diff --git a/examples/magento-graphcms/components/Layout/HeaderContainer.tsx b/examples/magento-graphcms/components/Layout/HeaderContainer.tsx new file mode 100644 index 00000000000..790de26bde8 --- /dev/null +++ b/examples/magento-graphcms/components/Layout/HeaderContainer.tsx @@ -0,0 +1,40 @@ +import type { ContainerSizingProps } from '@graphcommerce/next-ui' +import { Container, sxx } from '@graphcommerce/next-ui' + +export type HeaderContainerProps = ContainerSizingProps + +export function HeaderContainer(props: HeaderContainerProps) { + const { children, sx, ...containerProps } = props + + return ( + ({ + zIndex: theme.zIndex.appBar - 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: theme.appShell.headerHeightSm, + pointerEvents: 'none', + '& > *': { + pointerEvents: 'all', + }, + [theme.breakpoints.up('md')]: { + height: theme.appShell.headerHeightMd, + top: 0, + display: 'flex', + justifyContent: 'left', + width: '100%', + }, + }), + sx, + )} + > + {children} + + ) +} diff --git a/examples/magento-graphcms/components/Layout/LayoutDefault.tsx b/examples/magento-graphcms/components/Layout/LayoutDefault.tsx new file mode 100644 index 00000000000..2de89aaf05d --- /dev/null +++ b/examples/magento-graphcms/components/Layout/LayoutDefault.tsx @@ -0,0 +1,134 @@ +import { useScrollOffset } from '@graphcommerce/framer-next-pages' +import { dvh } from '@graphcommerce/framer-utils' +import { + Container, + extendableComponent, + LayoutProvider, + SkipLink, + sxx, + useFabSize, +} from '@graphcommerce/next-ui' +import type { SxProps, Theme } from '@mui/material' +import { Box } from '@mui/material' +import { useScroll, useTransform } from 'framer-motion' + +export type LayoutDefaultProps = { + className?: string + beforeHeader?: React.ReactNode + header: React.ReactNode + footer: React.ReactNode + menuFab?: React.ReactNode + cartFab?: React.ReactNode + children?: React.ReactNode + noSticky?: boolean + sx?: SxProps +} & OwnerState + +type OwnerState = { + noSticky?: boolean +} +const parts = ['root', 'fabs', 'header', 'children', 'footer'] as const +const { withState } = extendableComponent( + 'LayoutDefault', + parts, +) + +export function LayoutDefault(props: LayoutDefaultProps) { + const { + children, + header, + beforeHeader, + footer, + menuFab, + cartFab, + noSticky, + className, + sx = [], + } = props + + const { scrollY } = useScroll() + const scrollYOffset = useTransform( + [scrollY, useScrollOffset()], + ([y, offset]: number[]) => y + offset, + ) + + const classes = withState({ noSticky }) + const fabIconSize = useFabSize('responsive') + + return ( + ({ + minHeight: dvh(100), + '@supports (-webkit-touch-callout: none)': { + minHeight: '-webkit-fill-available', + }, + display: 'grid', + gridTemplateRows: { xs: 'auto 1fr auto', md: 'auto auto 1fr auto' }, + gridTemplateColumns: '100%', + background: theme.vars.palette.background.default, + }), + sx, + )} + > + + + {beforeHeader} + {header} + {menuFab || cartFab ? ( + ({ + display: 'flex', + justifyContent: 'space-between', + width: '100%', + height: 0, + zIndex: 'speedDial', + [theme.breakpoints.up('sm')]: { + position: 'sticky', + marginTop: `calc(${theme.appShell.headerHeightMd} * -1 - calc(${fabIconSize} / 2))`, + top: `calc(${theme.appShell.headerHeightMd} / 2 - (${fabIconSize} / 2))`, + }, + [theme.breakpoints.down('md')]: { + position: 'fixed', + top: 'unset', + bottom: `calc(20px + ${fabIconSize})`, + padding: '0 20px', + '@media (max-height: 530px) and (orientation: portrait)': { + display: 'none', + }, + }, + })} + > + {menuFab} + {cartFab && ( + ({ + display: 'flex', + flexDirection: 'row-reverse', + gap: theme.spacings.sm, + [theme.breakpoints.up('md')]: { + flexDirection: 'column', + alignItems: 'flex-end', + }, + })} + > + {cartFab} + + )} + + ) : ( +
+ )} +
+
+ {children} +
+
{footer}
+ + + ) +} diff --git a/examples/magento-graphcms/components/Layout/LayoutMinimal.tsx b/examples/magento-graphcms/components/Layout/LayoutMinimal.tsx index fd013541eaf..714433b4f1c 100644 --- a/examples/magento-graphcms/components/Layout/LayoutMinimal.tsx +++ b/examples/magento-graphcms/components/Layout/LayoutMinimal.tsx @@ -1,6 +1,7 @@ -import { LayoutDefault, LayoutDefaultProps } from '@graphcommerce/next-ui' import { Footer } from './Footer' +import { HeaderContainer } from './HeaderContainer' import { LayoutQuery } from './Layout.gql' +import { LayoutDefault, type LayoutDefaultProps } from './LayoutDefault' import { Logo } from './Logo' export type LayoutMinimalProps = LayoutQuery & @@ -12,7 +13,11 @@ export function LayoutMinimal(props: LayoutMinimalProps) { return ( } + header={ + + + + } footer={