Merge branch 'trunk' into hpos/unit-tests
This commit is contained in:
commit
ade1540ece
|
@ -29,7 +29,7 @@ runs:
|
|||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
|
||||
with:
|
||||
version: '^7.22.0'
|
||||
version: '7.29.1'
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
|
||||
|
|
|
@ -31,9 +31,9 @@ jobs:
|
|||
include:
|
||||
- wp: nightly
|
||||
php: '7.4'
|
||||
- wp: '5.9'
|
||||
- wp: '6.0'
|
||||
php: 7.4
|
||||
- wp: '5.8'
|
||||
- wp: '5.9'
|
||||
php: 7.4
|
||||
services:
|
||||
database:
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
# Duplicate workflow that returns success for this check when there is no relevant file change. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
|
||||
name: Status Check Bypass for Changelog Only Changes
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '!**'
|
||||
- '**/changelog/**'
|
||||
|
||||
jobs:
|
||||
bypass-lint:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Lint and Test JS"
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
bypass-7-4-latest:
|
||||
runs-on: ubuntu-latest
|
||||
name: "PHP 7.4 WP latest"
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
bypass-8-0-latest:
|
||||
runs-on: ubuntu-latest
|
||||
name: "PHP 8.0 WP latest"
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
bypass-api-tests:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Runs API tests."
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
bypass-k6:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Runs k6 Performance tests"
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
bypass-sniff:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Code sniff (PHP 7.4, WP Latest)"
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
bypass-changelogger-use:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Changelogger use"
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
bypass-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Runs E2E tests."
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
bypass-pr-highlight:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Check pull request changes to highlight"
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
"sass": "^1.49.9",
|
||||
"sass-loader": "^10.2.1",
|
||||
"syncpack": "^9.8.4",
|
||||
"turbo": "^1.7.0",
|
||||
"turbo": "^1.8.3",
|
||||
"typescript": "^4.8.3",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^5.70.0"
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
|
||||
Update showOtherPaymentMethods() to test latest payment task properly
|
|
@ -31,12 +31,6 @@ export class PaymentsSetup extends BasePage {
|
|||
}
|
||||
|
||||
async showOtherPaymentMethods(): Promise< void > {
|
||||
const selector = '.woocommerce-task-payments button.toggle-button';
|
||||
await this.page.waitForSelector( selector );
|
||||
const toggleButton = await this.page.$(
|
||||
`${ selector }[aria-expanded=false]`
|
||||
);
|
||||
await toggleButton?.click();
|
||||
await waitForElementByText( 'h2', 'Offline payment methods' );
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
package-lock=false
|
|
@ -0,0 +1,11 @@
|
|||
# Admin Layout
|
||||
|
||||
A collection of WooCommerce Admin layout components and utilities.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the module
|
||||
|
||||
```bash
|
||||
pnpm install @woocommerce/product-editor --save
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
# Changelog
|
||||
|
||||
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Create @woocommerce/admin-layout package to house header, footer, and similar components and utilities.
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "woocommerce/admin-layout",
|
||||
"description": "WooCommerce Admin layout component library",
|
||||
"type": "library",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"minimum-stability": "dev",
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "3.3.0"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.2"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"changelogger": {
|
||||
"formatter": {
|
||||
"filename": "../../../tools/changelogger/class-package-formatter.php"
|
||||
},
|
||||
"types": {
|
||||
"fix": "Fixes an existing bug",
|
||||
"add": "Adds functionality",
|
||||
"update": "Update existing functionality",
|
||||
"dev": "Development related task",
|
||||
"tweak": "A minor adjustment to the codebase",
|
||||
"performance": "Address performance issues",
|
||||
"enhancement": "Improve existing functionality"
|
||||
},
|
||||
"changelog": "CHANGELOG.md"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,483 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "5ce7bfd856ef579554b96ae2f7451072",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "automattic/jetpack-changelogger",
|
||||
"version": "v3.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Automattic/jetpack-changelogger.git",
|
||||
"reference": "8f63c829b8d1b0d7b1d5de93510d78523ed18959"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-changelogger/zipball/8f63c829b8d1b0d7b1d5de93510d78523ed18959",
|
||||
"reference": "8f63c829b8d1b0d7b1d5de93510d78523ed18959",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6",
|
||||
"symfony/console": "^3.4 || ^5.2 || ^6.0",
|
||||
"symfony/process": "^3.4 || ^5.2 || ^6.0",
|
||||
"wikimedia/at-ease": "^1.2 || ^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"wikimedia/testing-access-wrapper": "^1.0 || ^2.0",
|
||||
"yoast/phpunit-polyfills": "1.0.4"
|
||||
},
|
||||
"bin": [
|
||||
"bin/changelogger"
|
||||
],
|
||||
"type": "project",
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"branch-alias": {
|
||||
"dev-trunk": "3.3.x-dev"
|
||||
},
|
||||
"mirror-repo": "Automattic/jetpack-changelogger",
|
||||
"version-constants": {
|
||||
"::VERSION": "src/Application.php"
|
||||
},
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-changelogger/compare/${old}...${new}"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Automattic\\Jetpack\\Changelog\\": "lib",
|
||||
"Automattic\\Jetpack\\Changelogger\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Jetpack Changelogger tool. Allows for managing changelogs by dropping change files into a changelog directory with each PR.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-changelogger/tree/v3.3.0"
|
||||
},
|
||||
"time": "2022-12-26T13:49:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "1.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "d49695b909c3b7628b6289db5479a1c204601f11"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
|
||||
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Log\\": "Psr/Log/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for logging libraries",
|
||||
"homepage": "https://github.com/php-fig/log",
|
||||
"keywords": [
|
||||
"log",
|
||||
"psr",
|
||||
"psr-3"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/log/tree/1.1.4"
|
||||
},
|
||||
"time": "2021-05-03T11:20:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "3.4.x-dev",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "a10b1da6fc93080c180bba7219b5ff5b7518fe81"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/a10b1da6fc93080c180bba7219b5ff5b7518fe81",
|
||||
"reference": "a10b1da6fc93080c180bba7219b5ff5b7518fe81",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.5.9|>=7.0.8",
|
||||
"symfony/debug": "~2.8|~3.0|~4.0",
|
||||
"symfony/polyfill-mbstring": "~1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/dependency-injection": "<3.4",
|
||||
"symfony/process": "<3.3"
|
||||
},
|
||||
"provide": {
|
||||
"psr/log-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"psr/log": "~1.0",
|
||||
"symfony/config": "~3.3|~4.0",
|
||||
"symfony/dependency-injection": "~3.4|~4.0",
|
||||
"symfony/event-dispatcher": "~2.8|~3.0|~4.0",
|
||||
"symfony/lock": "~3.4|~4.0",
|
||||
"symfony/process": "~3.3|~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "For using the console logger",
|
||||
"symfony/event-dispatcher": "",
|
||||
"symfony/lock": "",
|
||||
"symfony/process": ""
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Console\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony Console Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-10-24T10:57:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "4.4.x-dev",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
"reference": "1a692492190773c5310bc7877cb590c04c2f05be"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/1a692492190773c5310bc7877cb590c04c2f05be",
|
||||
"reference": "1a692492190773c5310bc7877cb590c04c2f05be",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.3",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/http-kernel": "<3.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/http-kernel": "^3.4|^4.0|^5.0"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Debug\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides tools to ease debugging PHP code",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/debug/tree/v4.4.44"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"abandoned": "symfony/error-handler",
|
||||
"time": "2022-07-28T16:29:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "dev-main",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "f9c7affe77a00ae32ca127ca6833d034e6d33f25"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f9c7affe77a00ae32ca127ca6833d034e6d33f25",
|
||||
"reference": "f9c7affe77a00ae32ca127ca6833d034e6d33f25",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/main"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-30T17:25:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "3.4.x-dev",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "b8648cf1d5af12a44a51d07ef9bf980921f15fca"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/b8648cf1d5af12a44a51d07ef9bf980921f15fca",
|
||||
"reference": "b8648cf1d5af12a44a51d07ef9bf980921f15fca",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.5.9|>=7.0.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Process\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony Process Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-10-24T10:57:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "wikimedia/at-ease",
|
||||
"version": "v2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wikimedia/at-ease.git",
|
||||
"reference": "013ac61929797839c80a111a3f1a4710d8248e7a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/wikimedia/at-ease/zipball/013ac61929797839c80a111a3f1a4710d8248e7a",
|
||||
"reference": "013ac61929797839c80a111a3f1a4710d8248e7a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6.99"
|
||||
},
|
||||
"require-dev": {
|
||||
"jakub-onderka/php-console-highlighter": "0.3.2",
|
||||
"jakub-onderka/php-parallel-lint": "1.0.0",
|
||||
"mediawiki/mediawiki-codesniffer": "22.0.0",
|
||||
"mediawiki/minus-x": "0.3.1",
|
||||
"ockcyp/covers-validator": "0.5.1 || 0.6.1",
|
||||
"phpunit/phpunit": "4.8.36 || ^6.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Wikimedia/Functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Wikimedia\\AtEase\\": "src/Wikimedia/AtEase/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Tim Starling",
|
||||
"email": "tstarling@wikimedia.org"
|
||||
},
|
||||
{
|
||||
"name": "MediaWiki developers",
|
||||
"email": "wikitech-l@lists.wikimedia.org"
|
||||
}
|
||||
],
|
||||
"description": "Safe replacement to @ for suppressing warnings.",
|
||||
"homepage": "https://www.mediawiki.org/wiki/at-ease",
|
||||
"support": {
|
||||
"source": "https://github.com/wikimedia/at-ease/tree/master"
|
||||
},
|
||||
"time": "2018-10-10T15:39:06+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"platform-overrides": {
|
||||
"php": "7.2"
|
||||
},
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"name": "@woocommerce/admin-layout",
|
||||
"version": "1.0.0-beta.0",
|
||||
"description": "WooCommerce admin layout copmonents and utilities.",
|
||||
"author": "Automattic",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"keywords": [
|
||||
"wordpress",
|
||||
"woocommerce"
|
||||
],
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/admin-layout/README.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/woocommerce/woocommerce/issues"
|
||||
},
|
||||
"main": "build/index.js",
|
||||
"module": "build-module/index.js",
|
||||
"types": "build-types",
|
||||
"react-native": "src/index",
|
||||
"sideEffects": [
|
||||
"build-style/**",
|
||||
"src/**/*.scss"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"turbo:build": "pnpm run build:js && pnpm run build:css",
|
||||
"prepare": "composer install",
|
||||
"changelog": "composer exec -- changelogger",
|
||||
"clean": "pnpm exec rimraf tsconfig.tsbuildinfo build build-*",
|
||||
"build": "pnpm -w exec turbo run turbo:build --filter=$npm_package_name",
|
||||
"lint": "eslint src",
|
||||
"build:js": "tsc --build ./tsconfig.json ./tsconfig-cjs.json",
|
||||
"build:css": "webpack",
|
||||
"start": "concurrently \"tsc --build --watch\" \"webpack --watch\"",
|
||||
"prepack": "pnpm run clean && pnpm run build",
|
||||
"lint:fix": "eslint src --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/wordpress__components": "^19.10.3",
|
||||
"@woocommerce/eslint-plugin": "workspace:*",
|
||||
"@woocommerce/internal-style-build": "workspace:*",
|
||||
"@wordpress/browserslist-config": "wp-6.0",
|
||||
"css-loader": "^3.6.0",
|
||||
"eslint": "^8.32.0",
|
||||
"jest": "^27.5.1",
|
||||
"jest-cli": "^27.5.1",
|
||||
"postcss-loader": "^4.3.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"sass-loader": "^10.2.1",
|
||||
"ts-jest": "^27.1.3",
|
||||
"typescript": "^4.8.3",
|
||||
"webpack": "^5.70.0",
|
||||
"webpack-cli": "^3.3.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@woocommerce/components": "workspace:*",
|
||||
"@wordpress/components": "wp-6.0",
|
||||
"@wordpress/element": "wp-6.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './plugins';
|
|
@ -0,0 +1,10 @@
|
|||
export { WC_FOOTER_SLOT_NAME, WooFooterItem } from './woo-footer-item';
|
||||
export { WC_HEADER_SLOT_NAME, WooHeaderItem } from './woo-header-item';
|
||||
export {
|
||||
WC_HEADER_NAVIGATION_SLOT_NAME,
|
||||
WooHeaderNavigationItem,
|
||||
} from './woo-header-navigation-item';
|
||||
export {
|
||||
WC_HEADER_PAGE_TITLE_SLOT_NAME,
|
||||
WooHeaderPageTitle,
|
||||
} from './woo-header-page-title';
|
|
@ -1,14 +1,16 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Slot, Fill } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { createOrderedChildren, sortFillsByOrder } from '~/utils';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import {
|
||||
createOrderedChildren,
|
||||
sortFillsByOrder,
|
||||
} from '@woocommerce/components';
|
||||
|
||||
export const WC_FOOTER_SLOT_NAME = 'woocommerce_footer_item';
|
||||
|
||||
/**
|
||||
* Create a Fill for extensions to add items to the WooCommerce Admin footer.
|
||||
*
|
||||
|
@ -27,7 +29,10 @@ export const WC_FOOTER_SLOT_NAME = 'woocommerce_footer_item';
|
|||
* @param {Array} param0.children - Node children.
|
||||
* @param {Array} param0.order - Node order.
|
||||
*/
|
||||
export const WooFooterItem: React.FC< { order?: number } > & {
|
||||
export const WooFooterItem: React.FC< {
|
||||
children?: React.ReactNode;
|
||||
order?: number;
|
||||
} > & {
|
||||
Slot: React.FC< Slot.Props >;
|
||||
} = ( { children, order = 1 } ) => {
|
||||
return (
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Slot, Fill } from '@wordpress/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import {
|
||||
createOrderedChildren,
|
||||
sortFillsByOrder,
|
||||
} from '@woocommerce/components';
|
||||
|
||||
export const WC_HEADER_SLOT_NAME = 'woocommerce_header_item';
|
||||
|
||||
/**
|
||||
* Create a Fill for extensions to add items to the WooCommerce Admin header.
|
||||
*
|
||||
* @slotFill WooHeaderItem
|
||||
* @scope woocommerce-admin
|
||||
* @example
|
||||
* const MyHeaderItem = () => (
|
||||
* <WooHeaderItem>My header item</WooHeaderItem>
|
||||
* );
|
||||
*
|
||||
* registerPlugin( 'my-extension', {
|
||||
* render: MyHeaderItem,
|
||||
* scope: 'woocommerce-admin',
|
||||
* } );
|
||||
* @param {Object} param0
|
||||
* @param {Array} param0.children - Node children.
|
||||
* @param {Array} param0.order - Node order.
|
||||
*/
|
||||
export const WooHeaderItem: React.FC< {
|
||||
children?: React.ReactNode;
|
||||
order?: number;
|
||||
} > & {
|
||||
Slot: React.FC< Slot.Props >;
|
||||
} = ( { children, order = 1 } ) => {
|
||||
return (
|
||||
<Fill name={ WC_HEADER_SLOT_NAME }>
|
||||
{ ( fillProps: Fill.Props ) => {
|
||||
return createOrderedChildren( children, order, fillProps );
|
||||
} }
|
||||
</Fill>
|
||||
);
|
||||
};
|
||||
|
||||
WooHeaderItem.Slot = ( { fillProps } ) => (
|
||||
<Slot name={ WC_HEADER_SLOT_NAME } fillProps={ fillProps }>
|
||||
{ sortFillsByOrder }
|
||||
</Slot>
|
||||
);
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Slot, Fill } from '@wordpress/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import {
|
||||
createOrderedChildren,
|
||||
sortFillsByOrder,
|
||||
} from '@woocommerce/components';
|
||||
|
||||
export const WC_HEADER_NAVIGATION_SLOT_NAME =
|
||||
'woocommerce_header_navigation_item';
|
||||
|
||||
/**
|
||||
* Create a Fill for extensions to add items to the WooCommerce Admin
|
||||
* navigation area left of the page title.
|
||||
*
|
||||
* @slotFill WooHeaderNavigationItem
|
||||
* @scope woocommerce-admin
|
||||
* @example
|
||||
* const MyNavigationItem = () => (
|
||||
* <WooHeaderNavigationItem>My nav item</WooHeaderNavigationItem>
|
||||
* );
|
||||
*
|
||||
* registerPlugin( 'my-extension', {
|
||||
* render: MyNavigationItem,
|
||||
* scope: 'woocommerce-admin',
|
||||
* } );
|
||||
* @param {Object} param0
|
||||
* @param {Array} param0.children - Node children.
|
||||
* @param {Array} param0.order - Node order.
|
||||
*/
|
||||
export const WooHeaderNavigationItem: React.FC< {
|
||||
children?: React.ReactNode;
|
||||
order?: number;
|
||||
} > & {
|
||||
Slot: React.FC< Slot.Props >;
|
||||
} = ( { children, order = 1 } ) => {
|
||||
return (
|
||||
<Fill name={ WC_HEADER_NAVIGATION_SLOT_NAME }>
|
||||
{ ( fillProps: Fill.Props ) => {
|
||||
return createOrderedChildren( children, order, fillProps );
|
||||
} }
|
||||
</Fill>
|
||||
);
|
||||
};
|
||||
|
||||
WooHeaderNavigationItem.Slot = ( { fillProps }: Slot.Props ) => (
|
||||
<Slot name={ WC_HEADER_NAVIGATION_SLOT_NAME } fillProps={ fillProps }>
|
||||
{ sortFillsByOrder }
|
||||
</Slot>
|
||||
);
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Slot, Fill } from '@wordpress/components';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
|
||||
export const WC_HEADER_PAGE_TITLE_SLOT_NAME = 'woocommerce_header_page_title';
|
||||
|
||||
/**
|
||||
* Create a Fill for extensions to add custom page titles.
|
||||
*
|
||||
* @slotFill WooHeaderPageTitle
|
||||
* @scope woocommerce-admin
|
||||
* @example
|
||||
* const MyPageTitle = () => (
|
||||
* <WooHeaderPageTitle>My page title</WooHeaderPageTitle>
|
||||
* );
|
||||
*
|
||||
* registerPlugin( 'my-page-title', {
|
||||
* render: MyPageTitle,
|
||||
* scope: 'woocommerce-admin',
|
||||
* } );
|
||||
* @param {Object} param0
|
||||
* @param {Array} param0.children - Node children.
|
||||
*/
|
||||
export const WooHeaderPageTitle: React.FC< {
|
||||
children?: React.ReactNode;
|
||||
} > & {
|
||||
Slot: React.FC< Slot.Props >;
|
||||
} = ( { children } ) => {
|
||||
return <Fill name={ WC_HEADER_PAGE_TITLE_SLOT_NAME }>{ children }</Fill>;
|
||||
};
|
||||
|
||||
WooHeaderPageTitle.Slot = ( { fillProps } ) => (
|
||||
<Slot name={ WC_HEADER_PAGE_TITLE_SLOT_NAME } fillProps={ fillProps }>
|
||||
{ ( fills ) => {
|
||||
return <>{ [ ...fills ].pop() }</>;
|
||||
} }
|
||||
</Slot>
|
||||
);
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "../tsconfig-cjs",
|
||||
"compilerOptions": {
|
||||
"outDir": "build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "build-module",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"declarationDir": "./build-types"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { webpackConfig } = require( '@woocommerce/internal-style-build' );
|
||||
|
||||
module.exports = {
|
||||
mode: process.env.NODE_ENV || 'development',
|
||||
entry: {
|
||||
'build-style': __dirname + '/src/style.scss',
|
||||
},
|
||||
output: {
|
||||
path: __dirname,
|
||||
},
|
||||
module: {
|
||||
rules: webpackConfig.rules,
|
||||
},
|
||||
plugins: webpackConfig.plugins,
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Add CES data store to @woocommerce/customer-effort-score
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Add additional components to package.
|
|
@ -47,8 +47,10 @@
|
|||
"@types/testing-library__jest-dom": "^5.14.3",
|
||||
"@types/wordpress__components": "^19.10.3",
|
||||
"@types/wordpress__data": "^6.0.0",
|
||||
"@woocommerce/data": "workspace:*",
|
||||
"@woocommerce/eslint-plugin": "workspace:*",
|
||||
"@woocommerce/internal-style-build": "workspace:*",
|
||||
"@woocommerce/navigation": "workspace:*",
|
||||
"@wordpress/browserslist-config": "wp-6.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"css-loader": "^3.6.0",
|
||||
|
|
|
@ -9,7 +9,7 @@ import { useDispatch } from '@wordpress/data';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { CustomerFeedbackModal } from './customer-feedback-modal';
|
||||
import { CustomerFeedbackModal } from '../customer-feedback-modal';
|
||||
|
||||
const noop = () => {};
|
||||
|
|
@ -8,7 +8,7 @@ import { useDispatch } from '@wordpress/data';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { CustomerEffortScore } from '../customer-effort-score';
|
||||
import { CustomerEffortScore } from '..';
|
||||
|
||||
const noop = () => {};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const ALLOW_TRACKING_OPTION_NAME = 'woocommerce_allow_tracking';
|
|
@ -10,7 +10,7 @@ import {
|
|||
addCustomerEffortScoreExitPageListener,
|
||||
addExitPage,
|
||||
removeCustomerEffortScoreExitPageListener,
|
||||
} from './customer-effort-score-exit-page';
|
||||
} from '../../utils/customer-effort-score-exit-page';
|
||||
|
||||
export const useCustomerEffortScoreExitPageTracker = (
|
||||
pageId: string,
|
|
@ -1,5 +1,9 @@
|
|||
export * from './customer-effort-score';
|
||||
export * from './customer-feedback-simple';
|
||||
export * from './customer-feedback-modal';
|
||||
export * from './product-mvp-feedback-modal';
|
||||
export * from './feedback-modal';
|
||||
export * from './components/customer-effort-score';
|
||||
export * from './components/customer-feedback-simple';
|
||||
export * from './components/customer-feedback-modal';
|
||||
export * from './components/product-mvp-feedback-modal';
|
||||
export * from './components/feedback-modal';
|
||||
export * from './hooks/use-customer-effort-score-exit-page-tracker';
|
||||
export * from './store';
|
||||
export * from './utils/customer-effort-score-exit-page';
|
||||
export * from './constants';
|
||||
|
|
|
@ -11,7 +11,9 @@ import * as actions from './actions';
|
|||
import * as resolvers from './resolvers';
|
||||
import * as selectors from './selectors';
|
||||
import reducer from './reducer';
|
||||
import { STORE_KEY } from './constants';
|
||||
import { QUEUE_OPTION_NAME, STORE_KEY } from './constants';
|
||||
|
||||
export { QUEUE_OPTION_NAME, STORE_KEY };
|
||||
|
||||
export default registerStore( STORE_KEY, {
|
||||
actions,
|
|
@ -1,6 +1,6 @@
|
|||
@import 'customer-feedback-simple/customer-feedback-simple.scss';
|
||||
@import 'product-mvp-feedback-modal/product-mvp-feedback-modal.scss';
|
||||
@import 'feedback-modal/feedback-modal.scss';
|
||||
@import 'components/customer-feedback-simple/customer-feedback-simple.scss';
|
||||
@import 'components/product-mvp-feedback-modal/product-mvp-feedback-modal.scss';
|
||||
@import 'components/feedback-modal/feedback-modal.scss';
|
||||
|
||||
.woocommerce-customer-effort-score__selection {
|
||||
margin: 1em 0 1.5em 0;
|
||||
|
|
|
@ -9,7 +9,13 @@ import { getQuery } from '@woocommerce/navigation';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ALLOW_TRACKING_OPTION_NAME } from './constants';
|
||||
import { ALLOW_TRACKING_OPTION_NAME } from '../constants';
|
||||
|
||||
interface AdminWindow extends Window {
|
||||
pagenow?: string;
|
||||
adminpage?: string;
|
||||
}
|
||||
declare let window: AdminWindow;
|
||||
|
||||
const CUSTOMER_EFFORT_SCORE_EXIT_PAGE_KEY = 'customer-effort-score-exit-page';
|
||||
|
||||
|
@ -87,7 +93,7 @@ const eventListeners: Record< string, ( event: BeforeUnloadEvent ) => void > =
|
|||
/**
|
||||
* Adds unload event listener to add pageId to exit page list incase there were unsaved changes.
|
||||
*
|
||||
* @param {string} pageId the page id of the page being exited early.
|
||||
* @param {string} pageId the page id of the page being exited early.
|
||||
* @param {Function} hasUnsavedChanges callback to check if the page had unsaved changes.
|
||||
*/
|
||||
export const addCustomerEffortScoreExitPageListener = (
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = [
|
||||
// wc-admin packages
|
||||
'@woocommerce/admin-layout',
|
||||
'@woocommerce/components',
|
||||
'@woocommerce/csv-export',
|
||||
'@woocommerce/currency',
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add @woocommerce/admin-layout package.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add hook to check unsaved form changes before page navigation
|
|
@ -31,6 +31,7 @@
|
|||
"@wordpress/compose": "wp-6.0",
|
||||
"@wordpress/element": "wp-6.0",
|
||||
"@wordpress/hooks": "wp-6.0",
|
||||
"@wordpress/i18n": "wp-6.0",
|
||||
"@wordpress/notices": "wp-6.0",
|
||||
"@wordpress/url": "wp-6.0",
|
||||
"history": "^5.3.0",
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useContext, useEffect, useMemo } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { parseAdminUrl } from '@woocommerce/navigation';
|
||||
import {
|
||||
Location,
|
||||
UNSAFE_NavigationContext as NavigationContext,
|
||||
useLocation,
|
||||
} from 'react-router-dom';
|
||||
import { Location } from 'react-router-dom';
|
||||
import { useEffect, useMemo } from '@wordpress/element';
|
||||
|
||||
export default function usePreventLeavingPage(
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getHistory } from '../history';
|
||||
import { parseAdminUrl } from '../';
|
||||
|
||||
export const useConfirmUnsavedChanges = (
|
||||
hasUnsavedChanges: boolean,
|
||||
shouldConfirm?: ( path: URL, fromUrl: Location ) => boolean,
|
||||
/**
|
||||
|
@ -19,24 +20,24 @@ export default function usePreventLeavingPage(
|
|||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event#compatibility_notes
|
||||
*/
|
||||
message?: string
|
||||
) {
|
||||
) => {
|
||||
const confirmMessage = useMemo(
|
||||
() =>
|
||||
message ??
|
||||
__( 'Changes you made may not be saved.', 'woocommerce' ),
|
||||
[ message ]
|
||||
);
|
||||
const { navigator } = useContext( NavigationContext );
|
||||
const fromUrl = useLocation();
|
||||
const history = getHistory();
|
||||
|
||||
// This effect prevent react router from navigate and show
|
||||
// a confirmation message. It's a work around to beforeunload
|
||||
// because react router does not triggers that event.
|
||||
useEffect( () => {
|
||||
if ( hasUnsavedChanges ) {
|
||||
const push = navigator.push;
|
||||
const push = history.push;
|
||||
|
||||
navigator.push = ( ...args: Parameters< typeof push > ) => {
|
||||
history.push = ( ...args: Parameters< typeof push > ) => {
|
||||
const fromUrl = history.location;
|
||||
const toUrl = parseAdminUrl( args[ 0 ] ) as URL;
|
||||
if (
|
||||
typeof shouldConfirm === 'function' &&
|
||||
|
@ -54,10 +55,10 @@ export default function usePreventLeavingPage(
|
|||
};
|
||||
|
||||
return () => {
|
||||
navigator.push = push;
|
||||
history.push = push;
|
||||
};
|
||||
}
|
||||
}, [ navigator, hasUnsavedChanges, confirmMessage ] );
|
||||
}, [ history, hasUnsavedChanges, confirmMessage ] );
|
||||
|
||||
// This effect listen to the native beforeunload event to show
|
||||
// a confirmation message
|
||||
|
@ -79,4 +80,4 @@ export default function usePreventLeavingPage(
|
|||
};
|
||||
}
|
||||
}, [ hasUnsavedChanges, confirmMessage ] );
|
||||
}
|
||||
};
|
|
@ -18,9 +18,6 @@ import { getAdminLink } from '@woocommerce/settings';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { getHistory } from './history';
|
||||
import * as navUtils from './index';
|
||||
|
||||
// For the above, import the module into itself. Functions consumed from this import can be mocked in tests.
|
||||
|
||||
// Expose history so all uses get the same history object.
|
||||
export { getHistory };
|
||||
|
@ -28,6 +25,9 @@ export { getHistory };
|
|||
// Export all filter utilities
|
||||
export * from './filters';
|
||||
|
||||
// Export all hooks
|
||||
export { useConfirmUnsavedChanges } from './hooks/use-confirm-unsaved-changes';
|
||||
|
||||
const TIME_EXCLUDED_SCREENS_FILTER = 'woocommerce_admin_time_excluded_screens';
|
||||
|
||||
/**
|
||||
|
@ -79,7 +79,7 @@ export function getNewPath(
|
|||
* @param {Object} query Query containing the parameters.
|
||||
* @return {Object} Object containing the persisted queries.
|
||||
*/
|
||||
export const getPersistedQuery = ( query = navUtils.getQuery() ) => {
|
||||
export const getPersistedQuery = ( query = getQuery() ) => {
|
||||
/**
|
||||
* Filter persisted queries. These query parameters remain in the url when other parameters are updated.
|
||||
*
|
||||
|
@ -226,7 +226,7 @@ export function getIdsFromQuery( queryString = '' ) {
|
|||
* @param {Object} query Query object.
|
||||
* @return {Array} List of search words.
|
||||
*/
|
||||
export function getSearchWords( query = navUtils.getQuery() ) {
|
||||
export function getSearchWords( query = getQuery() ) {
|
||||
if ( typeof query !== 'object' ) {
|
||||
throw new Error(
|
||||
'Invalid parameter passed to getSearchWords, it expects an object or no parameters.'
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add section block for use in product editor
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add tabs block and tabs to product editor
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Improve accessibility around product editor tabs
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add tests around product block editor tabs
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: tweak
|
||||
|
||||
Export the ProductEditorSettings type.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Suppress errant TS lint errors.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Adding name field block to product editor.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Remove the product block breadcrumbs and sidebar inspector
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Update use of blocks within block editor to always make use of template.
|
|
@ -34,20 +34,23 @@
|
|||
"@woocommerce/components": "workspace:*",
|
||||
"@woocommerce/currency": "workspace:*",
|
||||
"@woocommerce/data": "workspace:^4.1.0",
|
||||
"@woocommerce/navigation": "workspace:^8.1.0",
|
||||
"@woocommerce/number": "workspace:*",
|
||||
"@woocommerce/tracks": "workspace:^1.3.0",
|
||||
"@wordpress/block-editor": "^9.8.0",
|
||||
"@wordpress/blocks": "^12.3.0",
|
||||
"@wordpress/data": "wp-6.0",
|
||||
"@wordpress/interface": "wp-6.0",
|
||||
"@wordpress/keyboard-shortcuts": "wp-6.0",
|
||||
"@wordpress/media-utils": "wp-6.0",
|
||||
"@wordpress/components": "wp-6.0",
|
||||
"@wordpress/compose": "wp-6.0",
|
||||
"@wordpress/core-data": "wp-6.0",
|
||||
"@wordpress/data": "wp-6.0",
|
||||
"@wordpress/editor": "wp-6.0",
|
||||
"@wordpress/element": "wp-6.0",
|
||||
"@wordpress/html-entities": "wp-6.0",
|
||||
"@wordpress/i18n": "wp-6.0",
|
||||
"@wordpress/icons": "wp-6.0",
|
||||
"@wordpress/interface": "wp-6.0",
|
||||
"@wordpress/keyboard-shortcuts": "wp-6.0",
|
||||
"@wordpress/media-utils": "wp-6.0",
|
||||
"@wordpress/url": "wp-6.0",
|
||||
"classnames": "^2.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -57,9 +60,12 @@
|
|||
"@testing-library/react": "^12.1.3",
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/wordpress__block-editor": "^7.0.0",
|
||||
"@types/wordpress__block-library": "^2.6.1",
|
||||
"@types/wordpress__blocks": "^11.0.7",
|
||||
"@types/wordpress__components": "^19.10.3",
|
||||
"@types/wordpress__core-data": "^2.4.5",
|
||||
"@types/wordpress__data": "^6.0.2",
|
||||
"@types/wordpress__editor": "^13.0.0",
|
||||
"@types/wordpress__media-utils": "^3.0.0",
|
||||
"@woocommerce/eslint-plugin": "workspace:*",
|
||||
"@woocommerce/internal-style-build": "workspace:*",
|
||||
|
|
|
@ -1,47 +1,62 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { BlockInstance } from '@wordpress/blocks';
|
||||
import { createElement, useState, useMemo } from '@wordpress/element';
|
||||
import { synchronizeBlocksWithTemplate, Template } from '@wordpress/blocks';
|
||||
import {
|
||||
createElement,
|
||||
useMemo,
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import { useSelect, select as WPSelect } from '@wordpress/data';
|
||||
import { uploadMedia } from '@wordpress/media-utils';
|
||||
import {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
BlockBreadcrumb,
|
||||
BlockContextProvider,
|
||||
BlockEditorKeyboardShortcuts,
|
||||
BlockEditorProvider,
|
||||
BlockList,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
BlockTools,
|
||||
BlockInspector,
|
||||
EditorSettings,
|
||||
EditorBlockListSettings,
|
||||
WritingFlow,
|
||||
ObserveTyping,
|
||||
} from '@wordpress/block-editor';
|
||||
// It doesn't seem to notice the External dependency block whn @ts-ignore is added.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore store should be included.
|
||||
useEntityBlockEditor,
|
||||
} from '@wordpress/core-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Sidebar } from '../sidebar';
|
||||
import { Tabs } from '../tabs';
|
||||
|
||||
type BlockEditorProps = {
|
||||
product: Partial< Product >;
|
||||
settings: Partial< EditorSettings & EditorBlockListSettings > | undefined;
|
||||
settings:
|
||||
| ( Partial< EditorSettings & EditorBlockListSettings > & {
|
||||
template?: Template[];
|
||||
} )
|
||||
| undefined;
|
||||
};
|
||||
|
||||
export function BlockEditor( { settings: _settings }: BlockEditorProps ) {
|
||||
const [ blocks, updateBlocks ] = useState< BlockInstance[] >();
|
||||
export function BlockEditor( {
|
||||
settings: _settings,
|
||||
product,
|
||||
}: BlockEditorProps ) {
|
||||
const [ selectedTab, setSelectedTab ] = useState< string | null >( null );
|
||||
|
||||
const canUserCreateMedia = useSelect( ( select: typeof WPSelect ) => {
|
||||
const { canUser } = select( 'core' ) as Record<
|
||||
string,
|
||||
( ...args: string[] ) => boolean
|
||||
>;
|
||||
return canUser( 'create', 'media' ) !== false;
|
||||
const { canUser } = select( 'core' );
|
||||
return canUser( 'create', 'media', '' ) !== false;
|
||||
}, [] );
|
||||
|
||||
const settings = useMemo( () => {
|
||||
|
@ -68,47 +83,47 @@ export function BlockEditor( { settings: _settings }: BlockEditorProps ) {
|
|||
};
|
||||
}, [ canUserCreateMedia, _settings ] );
|
||||
|
||||
/**
|
||||
* Wrapper for updating blocks. Required as `onInput` callback passed to
|
||||
* `BlockEditorProvider` is now called with more than 1 argument. Therefore
|
||||
* attempting to setState directly via `updateBlocks` will trigger an error
|
||||
* in React.
|
||||
*
|
||||
* @param _blocks
|
||||
*/
|
||||
function handleUpdateBlocks( _blocks: BlockInstance[] ) {
|
||||
updateBlocks( _blocks );
|
||||
}
|
||||
const [ blocks, onInput, onChange ] = useEntityBlockEditor(
|
||||
'postType',
|
||||
'product',
|
||||
{ id: product.id }
|
||||
);
|
||||
|
||||
function handlePersistBlocks( newBlocks: BlockInstance[] ) {
|
||||
updateBlocks( newBlocks );
|
||||
useLayoutEffect( () => {
|
||||
onChange(
|
||||
synchronizeBlocksWithTemplate( [], _settings?.template ),
|
||||
{}
|
||||
);
|
||||
}, [] );
|
||||
|
||||
if ( ! blocks ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="woocommerce-product-block-editor">
|
||||
<BlockEditorProvider
|
||||
value={ blocks }
|
||||
onInput={ handleUpdateBlocks }
|
||||
onChange={ handlePersistBlocks }
|
||||
settings={ settings }
|
||||
>
|
||||
<BlockBreadcrumb />
|
||||
<Sidebar.InspectorFill>
|
||||
<BlockInspector />
|
||||
</Sidebar.InspectorFill>
|
||||
<div className="editor-styles-wrapper">
|
||||
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
|
||||
{ /* @ts-ignore No types for this exist yet. */ }
|
||||
<BlockEditorKeyboardShortcuts.Register />
|
||||
<BlockTools>
|
||||
<WritingFlow>
|
||||
<ObserveTyping>
|
||||
<BlockList className="woocommerce-product-block-editor__block-list" />
|
||||
</ObserveTyping>
|
||||
</WritingFlow>
|
||||
</BlockTools>
|
||||
</div>
|
||||
</BlockEditorProvider>
|
||||
<BlockContextProvider value={ { selectedTab } }>
|
||||
<BlockEditorProvider
|
||||
value={ blocks }
|
||||
onInput={ onInput }
|
||||
onChange={ onChange }
|
||||
settings={ settings }
|
||||
>
|
||||
<Tabs onChange={ setSelectedTab } />
|
||||
<div className="editor-styles-wrapper">
|
||||
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
|
||||
{ /* @ts-ignore No types for this exist yet. */ }
|
||||
<BlockEditorKeyboardShortcuts.Register />
|
||||
<BlockTools>
|
||||
<WritingFlow>
|
||||
<ObserveTyping>
|
||||
<BlockList className="woocommerce-product-block-editor__block-list" />
|
||||
</ObserveTyping>
|
||||
</WritingFlow>
|
||||
</BlockTools>
|
||||
</div>
|
||||
</BlockEditorProvider>
|
||||
</BlockContextProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
.woocommerce-product-block-editor {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
input {
|
||||
font-family: var(--wp--preset--font-family--system-font);
|
||||
}
|
||||
|
||||
.editor-styles-wrapper {
|
||||
max-width: 650px;
|
||||
margin-left: auto;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 2,
|
||||
"name": "woocommerce/product-name",
|
||||
"title": "Product name",
|
||||
"category": "widgets",
|
||||
"description": "The product name.",
|
||||
"keywords": [ "products", "name", "title" ],
|
||||
"textdomain": "default",
|
||||
"attributes": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": false,
|
||||
"reusable": false,
|
||||
"inserter": false,
|
||||
"lock": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import interpolateComponents from '@automattic/interpolate-components';
|
||||
import { TextControl } from '@woocommerce/components';
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
|
||||
export function Edit() {
|
||||
const blockProps = useBlockProps();
|
||||
const [ name, setName ] = useEntityProp( 'postType', 'product', 'name' );
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<TextControl
|
||||
label={ interpolateComponents( {
|
||||
mixedString: __( 'Name {{required/}}', 'woocommerce' ),
|
||||
components: {
|
||||
required: (
|
||||
<span className="woocommerce-product-form__optional-input">
|
||||
{ __( '(required)', 'woocommerce' ) }
|
||||
</span>
|
||||
),
|
||||
},
|
||||
} ) }
|
||||
name={ 'woocommerce-product-name' }
|
||||
placeholder={ __( 'e.g. 12 oz Coffee Mug', 'woocommerce' ) }
|
||||
onChange={ setName }
|
||||
value={ name || '' }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { initBlock } from '../../utils';
|
||||
import metadata from './block.json';
|
||||
import { Edit } from './edit';
|
||||
|
||||
const { name } = metadata;
|
||||
|
||||
export { metadata, name };
|
||||
|
||||
export const settings = {
|
||||
example: {},
|
||||
edit: Edit,
|
||||
};
|
||||
|
||||
export const init = () => initBlock( { name, metadata, settings } );
|
|
@ -8,6 +8,11 @@ import {
|
|||
} from '@wordpress/block-editor';
|
||||
import { SlotFillProvider } from '@wordpress/components';
|
||||
import { Product } from '@woocommerce/data';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import { EntityProvider } from '@wordpress/core-data';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
|
@ -21,35 +26,37 @@ import { FullscreenMode, InterfaceSkeleton } from '@wordpress/interface';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { Header } from '../header';
|
||||
import { Sidebar } from '../sidebar';
|
||||
import { BlockEditor } from '../block-editor';
|
||||
import { initBlocks } from './init-blocks';
|
||||
|
||||
initBlocks();
|
||||
|
||||
export type ProductEditorSettings = Partial<
|
||||
EditorSettings & EditorBlockListSettings
|
||||
>;
|
||||
type EditorProps = {
|
||||
product: Product;
|
||||
settings: Partial< EditorSettings & EditorBlockListSettings > | undefined;
|
||||
settings: ProductEditorSettings | undefined;
|
||||
};
|
||||
|
||||
export function Editor( { product, settings }: EditorProps ) {
|
||||
return (
|
||||
<StrictMode>
|
||||
<ShortcutProvider>
|
||||
<FullscreenMode isActive={ false } />
|
||||
<SlotFillProvider>
|
||||
<InterfaceSkeleton
|
||||
header={ <Header title={ product.name } /> }
|
||||
sidebar={ <Sidebar /> }
|
||||
content={
|
||||
<BlockEditor
|
||||
settings={ settings }
|
||||
product={ product }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
</ShortcutProvider>
|
||||
<EntityProvider kind="postType" type="product" id={ product.id }>
|
||||
<ShortcutProvider>
|
||||
<FullscreenMode isActive={ false } />
|
||||
<SlotFillProvider>
|
||||
<InterfaceSkeleton
|
||||
header={ <Header title={ product.name } /> }
|
||||
content={
|
||||
<BlockEditor
|
||||
settings={ settings }
|
||||
product={ product }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
</ShortcutProvider>
|
||||
</EntityProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { init as initName } from '../details-name-block';
|
||||
import { init as initSection } from '../section';
|
||||
import { init as initTab } from '../tab';
|
||||
|
||||
export const initBlocks = () => {};
|
||||
export const initBlocks = () => {
|
||||
initName();
|
||||
initSection();
|
||||
initTab();
|
||||
};
|
||||
|
|
|
@ -10,4 +10,7 @@ export { DetailsFeatureField as __experimentalDetailsFeatureField } from './deta
|
|||
export { DetailsCategoriesField as __experimentalDetailsCategoriesField } from './details-categories-field';
|
||||
export { DetailsSummaryField as __experimentalDetailsSummaryField } from './details-summary-field';
|
||||
export { DetailsDescriptionField as __experimentalDetailsDescriptionField } from './details-description-field';
|
||||
export { Editor as __experimentalEditor } from './editor';
|
||||
export {
|
||||
Editor as __experimentalEditor,
|
||||
ProductEditorSettings,
|
||||
} from './editor';
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 2,
|
||||
"name": "woocommerce/product-section",
|
||||
"title": "Product section",
|
||||
"category": "woocommerce",
|
||||
"description": "The product section.",
|
||||
"keywords": [ "products", "section", "group" ],
|
||||
"textdomain": "default",
|
||||
"attributes": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": true,
|
||||
"reusable": false,
|
||||
"inserter": false,
|
||||
"lock": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
|
||||
import type { BlockAttributes } from '@wordpress/blocks';
|
||||
|
||||
export function Edit( { attributes }: { attributes: BlockAttributes } ) {
|
||||
const blockProps = useBlockProps();
|
||||
const { description, title } = attributes;
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<h2 className="wp-block-woocommerce-product-section__title">
|
||||
{ title }
|
||||
</h2>
|
||||
<p className="wp-block-woocommerce-product-section__description">
|
||||
{ description }
|
||||
</p>
|
||||
<InnerBlocks templateLock="all" />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import initBlock from '../../utils/init-block';
|
||||
import metadata from './block.json';
|
||||
import { Edit } from './edit';
|
||||
|
||||
const { name } = metadata;
|
||||
|
||||
export { metadata, name };
|
||||
|
||||
export const settings = {
|
||||
example: {},
|
||||
edit: Edit,
|
||||
};
|
||||
|
||||
export const init = () => initBlock( { name, metadata, settings } );
|
|
@ -0,0 +1,18 @@
|
|||
.wp-block-woocommerce-product-section {
|
||||
margin-top: 48px;
|
||||
|
||||
&__title {
|
||||
margin-top: 0;
|
||||
margin-bottom: $gap-small;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin-top: $gap-small;
|
||||
margin-bottom: 32px;
|
||||
font-size: 13px;
|
||||
color: $gray-700;
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export * from './sidebar';
|
|
@ -1,30 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { createSlotFill, Panel } from '@wordpress/components';
|
||||
|
||||
const { Slot: InspectorSlot, Fill: InspectorFill } = createSlotFill(
|
||||
'ProductBlockEditorSidebarInspector'
|
||||
);
|
||||
|
||||
export function Sidebar() {
|
||||
return (
|
||||
<div
|
||||
className="woocommerce-product-sidebar"
|
||||
role="region"
|
||||
aria-label={ __(
|
||||
'Product Block Editor advanced settings.',
|
||||
'woocommerce'
|
||||
) }
|
||||
tabIndex={ -1 }
|
||||
>
|
||||
<Panel header={ __( 'Inspector', 'woocommerce' ) }>
|
||||
<InspectorSlot bubblesVirtually />
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Sidebar.InspectorFill = InspectorFill;
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 2,
|
||||
"name": "woocommerce/product-tab",
|
||||
"title": "Product tab",
|
||||
"category": "woocommerce",
|
||||
"description": "The product tab.",
|
||||
"keywords": [ "products", "tab", "group" ],
|
||||
"textdomain": "default",
|
||||
"attributes": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": true,
|
||||
"reusable": false,
|
||||
"inserter": false,
|
||||
"lock": false
|
||||
},
|
||||
"usesContext": [ "selectedTab" ]
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
|
||||
import type { BlockAttributes } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { TabButton } from './tab-button';
|
||||
|
||||
export function Edit( {
|
||||
attributes,
|
||||
context,
|
||||
}: {
|
||||
attributes: BlockAttributes;
|
||||
context?: {
|
||||
selectedTab?: string | null;
|
||||
};
|
||||
} ) {
|
||||
const blockProps = useBlockProps();
|
||||
const { id, title } = attributes;
|
||||
const isSelected = context?.selectedTab === id;
|
||||
|
||||
const classes = classnames( 'wp-block-woocommerce-product-tab__content', {
|
||||
'is-selected': isSelected,
|
||||
} );
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<TabButton id={ id } selected={ isSelected }>
|
||||
{ title }
|
||||
</TabButton>
|
||||
<div
|
||||
id={ `woocommerce-product-tab__${ id }-content` }
|
||||
aria-labelledby={ `woocommerce-product-tab__${ id }` }
|
||||
role="tabpanel"
|
||||
className={ classes }
|
||||
>
|
||||
<InnerBlocks templateLock="all" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import initBlock from '../../utils/init-block';
|
||||
import metadata from './block.json';
|
||||
import { Edit } from './edit';
|
||||
|
||||
const { name } = metadata;
|
||||
|
||||
export { metadata, name };
|
||||
|
||||
export const settings = {
|
||||
example: {},
|
||||
edit: Edit,
|
||||
};
|
||||
|
||||
export const init = () => initBlock( { name, metadata, settings } );
|
|
@ -0,0 +1,5 @@
|
|||
.wp-block-woocommerce-product-tab__content {
|
||||
&:not(.is-selected) {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Button, Fill } from '@wordpress/components';
|
||||
import classnames from 'classnames';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { TABS_SLOT_NAME } from '../tabs/constants';
|
||||
import { TabsFillProps } from '../tabs';
|
||||
|
||||
export function TabButton( {
|
||||
children,
|
||||
className,
|
||||
id,
|
||||
selected = false,
|
||||
}: {
|
||||
children: string | JSX.Element;
|
||||
className?: string;
|
||||
id: string;
|
||||
selected?: boolean;
|
||||
} ) {
|
||||
const classes = classnames(
|
||||
'wp-block-woocommerce-product-tab__button',
|
||||
className,
|
||||
{ 'is-selected': selected }
|
||||
);
|
||||
|
||||
return (
|
||||
<Fill name={ TABS_SLOT_NAME }>
|
||||
{ ( fillProps: TabsFillProps ) => {
|
||||
const { onClick } = fillProps;
|
||||
return (
|
||||
<Button
|
||||
key={ id }
|
||||
className={ classes }
|
||||
onClick={ () => onClick( id ) }
|
||||
id={ `woocommerce-product-tab__${ id }` }
|
||||
aria-controls={ `woocommerce-product-tab__${ id }-content` }
|
||||
aria-selected={ selected }
|
||||
>
|
||||
{ children }
|
||||
</Button>
|
||||
);
|
||||
} }
|
||||
</Fill>
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export const TABS_SLOT_NAME = 'woocommerce_product_tabs';
|
|
@ -0,0 +1 @@
|
|||
export * from './tabs';
|
|
@ -0,0 +1,17 @@
|
|||
.woocommerce-product-tabs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.components-button {
|
||||
padding: $gap-smaller 0 20px 0;
|
||||
margin-left: $gap;
|
||||
margin-right: $gap;
|
||||
border-bottom: 3.5px solid transparent;
|
||||
border-radius: 0;
|
||||
height: auto;
|
||||
|
||||
&.is-selected {
|
||||
border-color: var(--wp-admin-theme-color);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
createElement,
|
||||
Fragment,
|
||||
useEffect,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import { ReactElement } from 'react';
|
||||
import { NavigableMenu, Slot } from '@wordpress/components';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import { navigateTo, getNewPath, getQuery } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { TABS_SLOT_NAME } from './constants';
|
||||
|
||||
type TabsProps = {
|
||||
onChange?: ( tabId: string | null ) => void;
|
||||
};
|
||||
|
||||
export type TabsFillProps = {
|
||||
onClick: ( tabId: string ) => void;
|
||||
};
|
||||
|
||||
export function Tabs( { onChange = () => {} }: TabsProps ) {
|
||||
const [ selected, setSelected ] = useState< string | null >( null );
|
||||
const query = getQuery() as Record< string, string >;
|
||||
|
||||
function onClick( tabId: string ) {
|
||||
window.document.documentElement.scrollTop = 0;
|
||||
navigateTo( {
|
||||
url: getNewPath( { tab: tabId } ),
|
||||
} );
|
||||
}
|
||||
|
||||
useEffect( () => {
|
||||
onChange( selected );
|
||||
}, [ selected ] );
|
||||
|
||||
useEffect( () => {
|
||||
if ( query.tab ) {
|
||||
setSelected( query.tab );
|
||||
}
|
||||
}, [ query.tab ] );
|
||||
|
||||
function maybeSetSelected( fills: readonly ( readonly ReactElement[] )[] ) {
|
||||
if ( selected ) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ( let i = 0; i < fills.length; i++ ) {
|
||||
if ( fills[ i ][ 0 ].props.disabled ) {
|
||||
continue;
|
||||
}
|
||||
// Remove the `.$` prefix on keys. E.g., .$key => key
|
||||
const tabId = fills[ i ][ 0 ].key?.toString().slice( 2 ) || null;
|
||||
setSelected( tabId );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function selectTabOnNavigate(
|
||||
_childIndex: number,
|
||||
child: HTMLButtonElement
|
||||
) {
|
||||
child.click();
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigableMenu
|
||||
role="tablist"
|
||||
onNavigate={ selectTabOnNavigate }
|
||||
className="woocommerce-product-tabs"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Slot
|
||||
fillProps={
|
||||
{
|
||||
onClick,
|
||||
} as TabsFillProps
|
||||
}
|
||||
name={ TABS_SLOT_NAME }
|
||||
>
|
||||
{ ( fills ) => {
|
||||
maybeSetSelected( fills );
|
||||
return <>{ fills }</>;
|
||||
} }
|
||||
</Slot>
|
||||
</NavigableMenu>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { getQuery, navigateTo } from '@woocommerce/navigation';
|
||||
import React, { createElement } from 'react';
|
||||
import { SlotFillProvider } from '@wordpress/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Tabs } from '../';
|
||||
import { Edit as Tab } from '../../tab/edit';
|
||||
|
||||
jest.mock( '@wordpress/block-editor', () => ( {
|
||||
...jest.requireActual( '@wordpress/block-editor' ),
|
||||
useBlockProps: jest.fn(),
|
||||
} ) );
|
||||
|
||||
jest.mock( '@woocommerce/navigation', () => ( {
|
||||
...jest.requireActual( '@woocommerce/navigation' ),
|
||||
navigateTo: jest.fn(),
|
||||
getQuery: jest.fn().mockReturnValue( {} ),
|
||||
} ) );
|
||||
|
||||
function MockTabs( { onChange = jest.fn() } ) {
|
||||
const [ selected, setSelected ] = useState< string | null >( null );
|
||||
const mockContext = {
|
||||
selectedTab: selected,
|
||||
};
|
||||
|
||||
return (
|
||||
<SlotFillProvider>
|
||||
<Tabs
|
||||
onChange={ ( tabId ) => {
|
||||
setSelected( tabId );
|
||||
onChange( tabId );
|
||||
} }
|
||||
/>
|
||||
<Tab
|
||||
attributes={ { id: 'test1', title: 'Test button 1' } }
|
||||
context={ mockContext }
|
||||
/>
|
||||
<Tab
|
||||
attributes={ { id: 'test2', title: 'Test button 2' } }
|
||||
context={ mockContext }
|
||||
/>
|
||||
<Tab
|
||||
attributes={ { id: 'test3', title: 'Test button 3' } }
|
||||
context={ mockContext }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe( 'Tabs', () => {
|
||||
beforeEach( () => {
|
||||
( getQuery as jest.Mock ).mockReturnValue( {
|
||||
tab: null,
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'should render tab buttons added to the slot', () => {
|
||||
const { queryByText } = render( <MockTabs /> );
|
||||
expect( queryByText( 'Test button 1' ) ).toBeInTheDocument();
|
||||
expect( queryByText( 'Test button 2' ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should set the first tab as active initially', () => {
|
||||
const { queryByText } = render( <MockTabs /> );
|
||||
expect( queryByText( 'Test button 1' ) ).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'true'
|
||||
);
|
||||
expect( queryByText( 'Test button 2' ) ).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'false'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should navigate to a new URL when a tab is clicked', () => {
|
||||
const { getByText } = render( <MockTabs /> );
|
||||
const button = getByText( 'Test button 2' );
|
||||
fireEvent.click( button );
|
||||
|
||||
expect( navigateTo ).toHaveBeenLastCalledWith( {
|
||||
url: 'admin.php?page=wc-admin&tab=test2',
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'should select the tab provided in the URL initially', () => {
|
||||
( getQuery as jest.Mock ).mockReturnValue( {
|
||||
tab: 'test2',
|
||||
} );
|
||||
|
||||
const { getByText } = render( <MockTabs /> );
|
||||
|
||||
expect( getByText( 'Test button 2' ) ).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'true'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should select the tab provided on URL change', () => {
|
||||
const { getByText, rerender } = render( <MockTabs /> );
|
||||
|
||||
( getQuery as jest.Mock ).mockReturnValue( {
|
||||
tab: 'test3',
|
||||
} );
|
||||
|
||||
rerender( <MockTabs /> );
|
||||
|
||||
expect( getByText( 'Test button 3' ) ).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'true'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should call the onChange props when changing', async () => {
|
||||
const mockOnChange = jest.fn();
|
||||
const { rerender } = render( <MockTabs onChange={ mockOnChange } /> );
|
||||
|
||||
expect( mockOnChange ).toHaveBeenCalledWith( 'test1' );
|
||||
|
||||
( getQuery as jest.Mock ).mockReturnValue( {
|
||||
tab: 'test2',
|
||||
} );
|
||||
|
||||
rerender( <MockTabs onChange={ mockOnChange } /> );
|
||||
|
||||
expect( mockOnChange ).toHaveBeenCalledWith( 'test2' );
|
||||
} );
|
||||
|
||||
it( 'should add a class to the initially selected tab panel', async () => {
|
||||
const { getByRole } = render( <MockTabs /> );
|
||||
const panel1 = getByRole( 'tabpanel', { name: 'Test button 1' } );
|
||||
const panel2 = getByRole( 'tabpanel', { name: 'Test button 2' } );
|
||||
|
||||
expect( panel1.classList ).toContain( 'is-selected' );
|
||||
expect( panel2.classList ).not.toContain( 'is-selected' );
|
||||
} );
|
||||
|
||||
it( 'should add a class to the newly selected tab panel', async () => {
|
||||
const { getByText, getByRole, rerender } = render( <MockTabs /> );
|
||||
const button = getByText( 'Test button 2' );
|
||||
fireEvent.click( button );
|
||||
const panel1 = getByRole( 'tabpanel', { name: 'Test button 1' } );
|
||||
const panel2 = getByRole( 'tabpanel', { name: 'Test button 2' } );
|
||||
|
||||
( getQuery as jest.Mock ).mockReturnValue( {
|
||||
tab: 'test2',
|
||||
} );
|
||||
|
||||
rerender( <MockTabs /> );
|
||||
|
||||
expect( panel1.classList ).not.toContain( 'is-selected' );
|
||||
expect( panel2.classList ).toContain( 'is-selected' );
|
||||
} );
|
||||
} );
|
|
@ -5,3 +5,6 @@
|
|||
@import 'components/details-categories-field/create-category-modal.scss';
|
||||
@import 'components/header/style.scss';
|
||||
@import 'components/block-editor/style.scss';
|
||||
@import 'components/section/style.scss';
|
||||
@import 'components/tab/style.scss';
|
||||
@import 'components/tabs/style.scss';
|
||||
|
|
|
@ -19,6 +19,7 @@ import { preventLeavingProductForm } from './prevent-leaving-product-form';
|
|||
|
||||
export * from './create-ordered-children';
|
||||
export * from './sort-fills-by-order';
|
||||
export * from './init-blocks';
|
||||
|
||||
export {
|
||||
AUTO_DRAFT_NAME,
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
BlockConfiguration,
|
||||
BlockEditProps,
|
||||
registerBlockType,
|
||||
} from '@wordpress/blocks';
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
type BlockRepresentation = {
|
||||
name: string;
|
||||
metadata: BlockConfiguration;
|
||||
settings: Partial< Omit< BlockConfiguration, 'edit' > > & {
|
||||
readonly edit?:
|
||||
| ComponentType<
|
||||
BlockEditProps< object > & {
|
||||
context?: Record< string, unknown >;
|
||||
}
|
||||
>
|
||||
| undefined;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to register an individual block.
|
||||
*
|
||||
* @param {Object} block The block to be registered.
|
||||
*
|
||||
* @return {?WPBlockType} The block, if it has been successfully registered;
|
||||
* otherwise `undefined`.
|
||||
*/
|
||||
export default function initBlock( block: BlockRepresentation ) {
|
||||
if ( ! block ) {
|
||||
return;
|
||||
}
|
||||
const { metadata, settings, name } = block;
|
||||
return registerBlockType( { name, ...metadata }, settings );
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { BlockConfiguration, registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
interface BlockRepresentation {
|
||||
name: string;
|
||||
metadata: BlockConfiguration;
|
||||
settings: Partial< BlockConfiguration >;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to register an individual block.
|
||||
*
|
||||
* @param {Object} block The block to be registered.
|
||||
*
|
||||
* @return {?WPBlockType} The block, if it has been successfully registered;
|
||||
* otherwise `undefined`.
|
||||
*/
|
||||
export const initBlock = ( block: BlockRepresentation ) => {
|
||||
if ( ! block ) {
|
||||
return;
|
||||
}
|
||||
const { metadata, settings, name } = block;
|
||||
return registerBlockType( { name, ...metadata }, settings );
|
||||
};
|
|
@ -1,6 +1,11 @@
|
|||
{
|
||||
"extends": "../tsconfig-cjs",
|
||||
"include": [
|
||||
"**/*",
|
||||
"src/**/*.json"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "build"
|
||||
"outDir": "build",
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue