Merge branch 'woocommerce:trunk' into add/36413-support-for-cart-checkout-in-declare-compatibility

This commit is contained in:
Saad Tarhi 2023-02-23 08:38:03 +01:00 committed by GitHub
commit 2ed94d56c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
880 changed files with 22059 additions and 10923 deletions

View File

@ -1,27 +1,46 @@
codecov:
notify:
require_ci_to_pass: yes
notify:
require_ci_to_pass: yes
ignore:
- '**/tests'
- '**/test'
- 'tools/**'
- 'packages/js/admin-e2e-tests'
- 'packages/js/api-core-tests'
- 'packages/js/create-woo-extension'
- 'packages/js/e2e-core-tests'
- 'packages/js/e2e-environment'
- 'packages/js/e2e-utils'
- 'packages/js/eslint-plugin'
- 'packages/js/internal-e2e-builds'
- 'packages/js/internal-js-tests'
- 'packages/js/internal-style-build'
- '**/*.test.*'
coverage:
precision: 2
round: nearest
range: "50...100"
status:
project:
default:
informational: true
patch:
default:
informational: true
changes: off
precision: 1
round: nearest
range: '50...80'
status:
project:
default:
target: auto
patch:
default:
target: auto
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment: false
comment:
layout: 'reach, diff, flags, files'
behavior: default
require_changes: false
require_base: no
require_head: yes

View File

@ -14,15 +14,13 @@
Closes # .
<!-- The next section is mandatory. If your PR doesn't require testing, please indicate that you are purposefully omitting instructions. -->
- [ ] This PR is a very minor change/addition and does not require testing instructions (if checked you can ignore/remove the next section).
<!-- Begin testing instructions -->
### How to test the changes in this Pull Request:
<!-- Otherwise, please include detailed instructions on how these changes can be tested (including pre-conditions, configuration, steps to take and expected results). It may help to write your instructions using pseudocode -- as if you're telling a computer how to execute the test. -->
<!-- Please include detailed instructions on how these changes can be tested, make sure to review and follow the guide for writing high-quality testing instructions below. -->
- [ ] Have you followed the [Writing high-quality testing instructions guide](https://github.com/woocommerce/woocommerce/wiki/Writing-high-quality-testing-instructions)?
1.
2.
@ -35,6 +33,7 @@ Closes # .
- [ ] Have you added an explanation of what your changes do and why you'd like us to include them?
- [ ] Have you written new tests for your changes, as applicable?
- [ ] Have you created a changelog file for each project being changed, ie `pnpm --filter=<project> changelog add`?
- [ ] Have you included testing instructions?
<!-- Mark completed items with an [x] -->

View File

@ -27,26 +27,26 @@ runs:
echo "BUILD_FILTERS=$(node ./.github/actions/setup-woocommerce-monorepo/scripts/parse-input-filter.js '${{ inputs.build-filters }}')" >> $GITHUB_OUTPUT
- name: Setup PNPM
uses: pnpm/action-setup@10693b3829bf86eb2572aef5f3571dcf5ca9287d
uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
with:
version: '^7.13.3'
version: '^7.22.0'
- name: Setup Node
uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
with:
node-version-file: .nvmrc
cache: pnpm
registry-url: 'https://registry.npmjs.org'
- name: Setup PHP
uses: shivammathur/setup-php@e04e1d97f0c0481c6e1ba40f8a538454fe5d7709
uses: shivammathur/setup-php@8e2ac35f639d3e794c1da1f28999385ab6fdf0fc
with:
php-version: ${{ inputs.php-version }}
coverage: none
tools: phpcs, sirbrillig/phpcs-changed
- name: Cache Composer Dependencies
uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77
uses: actions/cache@58c146cc91c5b9e778e71775dfe9bf1442ad9a12
with:
path: ~/.cache/composer/files
key: ${{ runner.os }}-php-${{ inputs.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
@ -59,7 +59,7 @@ runs:
pnpm install ${{ steps.parse-input.outputs.INSTALL_FILTERS }}
- name: Cache Build Output
uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77
uses: actions/cache@58c146cc91c5b9e778e71775dfe9bf1442ad9a12
with:
path: node_modules/.cache/turbo
key: ${{ runner.os }}-build-output-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}

View File

@ -17,14 +17,14 @@ jobs:
name: Verify
runs-on: ubuntu-20.04
permissions:
contents: read
pull-requests: write
issues: write
contents: read
pull-requests: write
issues: write
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
- name: Install Octokit
run: npm --prefix .github/workflows/scripts install @octokit/action

View File

@ -118,102 +118,3 @@ jobs:
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
# test-summary:
# name: Post test results
# if: |
# always() &&
# ! github.event.pull_request.head.repo.fork &&
# (
# contains( needs.*.result, 'success' ) ||
# contains( needs.*.result, 'failure' )
# )
# runs-on: ubuntu-20.04
# permissions:
# contents: read
# needs: [cot-api-tests-run, cot-e2e-tests-run]
# steps:
# - name: Create dirs
# run: |
# mkdir -p repo
# mkdir -p artifacts/api
# mkdir -p artifacts/e2e
# mkdir -p output
#
# - name: Checkout code
# uses: actions/checkout@v3
# with:
# path: repo
#
# - name: Download API test report artifact
# uses: actions/download-artifact@v3
# with:
# name: api-test-report---pr-${{ github.event.number }}
# path: artifacts/api
#
# - name: Download Playwright E2E test report artifact
# uses: actions/download-artifact@v3
# with:
# name: e2e-test-report---pr-${{ github.event.number }}
# path: artifacts/e2e
#
# - name: Prepare test summary
# id: prepare-test-summary
# uses: actions/github-script@v6
# env:
# API_SUMMARY_PATH: ${{ github.workspace }}/artifacts/api/allure-report/widgets/summary.json
# E2E_PW_SUMMARY_PATH: ${{ github.workspace }}/artifacts/e2e/allure-report/widgets/summary.json
# PR_NUMBER: ${{ github.event.number }}
# SHA: ${{ github.event.pull_request.head.sha }}
# with:
# result-encoding: string
# script: |
# const script = require( './repo/.github/workflows/scripts/prepare-test-summary.js' )
# return await script( { core } )
#
# - name: Find PR comment by github-actions[bot]
# uses: peter-evans/find-comment@v2
# id: find-comment
# with:
# issue-number: ${{ github.event.pull_request.number }}
# comment-author: 'github-actions[bot]'
# body-includes: Test Results Summary
#
# - name: Create or update PR comment
# uses: peter-evans/create-or-update-comment@v2
# with:
# comment-id: ${{ steps.find-comment.outputs.comment-id }}
# issue-number: ${{ github.event.pull_request.number }}
# body: ${{ steps.prepare-test-summary.outputs.result }}
# edit-mode: replace
#
# publish-test-reports:
# name: Publish test reports
# if: |
# always() &&
# ! github.event.pull_request.head.repo.fork &&
# (
# contains( needs.*.result, 'success' ) ||
# contains( needs.*.result, 'failure' )
# )
# runs-on: ubuntu-20.04
# needs: [cot-api-tests-run, cot-e2e-tests-run]
# env:
# GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
# PR_NUMBER: ${{ github.event.number }}
# RUN_ID: ${{ github.run_id }}
# COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
# steps:
# - name: Publish test reports
# env:
# API_ARTIFACT: api-test-report---pr-${{ github.event.number }}
# E2E_ARTIFACT: e2e-test-report---pr-${{ github.event.number }}
# run: |
# gh workflow run publish-test-reports-pr.yml \
# -f run_id=$RUN_ID \
# -f api_artifact=$API_ARTIFACT \
# -f e2e_artifact=$E2E_ARTIFACT \
# -f pr_number=$PR_NUMBER \
# -f commit_sha=$COMMIT_SHA \
# -f s3_root=public \
# --repo woocommerce/woocommerce-test-reports

View File

@ -23,7 +23,7 @@ jobs:
uses: ./.github/actions/setup-woocommerce-monorepo
- name: Lint
run: pnpm run -r --filter='woocommerce/client/admin...' --filter='!@woocommerce/e2e*' --filter='!@woocommerce/api' --color lint
run: pnpm run -r --filter='release-posts' --filter='woocommerce/client/admin...' --filter='!@woocommerce/e2e*' --filter='!@woocommerce/api' --color lint
- name: Test
run: pnpm run test --filter='woocommerce/client/admin...' --filter='!@woocommerce/e2e*' --filter='!@woocommerce/api' --color

View File

@ -13,63 +13,50 @@ concurrency:
permissions: {}
jobs:
test:
name: PHP ${{ matrix.php }} WP ${{ matrix.wp }}
timeout-minutes: 30
runs-on: ubuntu-20.04
permissions:
contents: read
continue-on-error: ${{ matrix.wp == 'nightly' }}
strategy:
fail-fast: false
matrix:
php: ['7.4', '8.0']
wp: ['latest']
include:
- wp: nightly
php: '7.4'
- wp: '5.9'
php: 7.4
- wp: '5.8'
php: 7.4
services:
database:
image: mysql:5.6
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
steps:
- uses: actions/checkout@v3
test:
name: PHP ${{ matrix.php }} WP ${{ matrix.wp }}
timeout-minutes: 30
runs-on: ubuntu-20.04
permissions:
contents: read
continue-on-error: ${{ matrix.wp == 'nightly' }}
strategy:
fail-fast: false
matrix:
php: [ '7.4', '8.0' ]
wp: [ "latest" ]
include:
- wp: nightly
php: '7.4'
- wp: '6.0'
php: 7.4
- wp: '5.9'
php: 7.4
services:
database:
image: mysql:5.6
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
php-version: ${{ matrix.php }}
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
php-version: ${{ matrix.php }}
- name: Tool versions
run: |
php --version
composer --version
- name: Tool versions
run: |
php --version
composer --version
- name: Add PHP8 Compatibility.
run: |
if [ "$(php -r "echo version_compare(PHP_VERSION,'8.0','>=');")" ]; then
cd plugins/woocommerce
curl -L https://github.com/woocommerce/phpunit/archive/add-compatibility-with-php8-to-phpunit-7.zip -o /tmp/phpunit-7.5-fork.zip
unzip -d /tmp/phpunit-7.5-fork /tmp/phpunit-7.5-fork.zip
composer bin phpunit config --unset platform
composer bin phpunit config repositories.0 '{"type": "path", "url": "/tmp/phpunit-7.5-fork/phpunit-add-compatibility-with-php8-to-phpunit-7", "options": {"symlink": false}}'
composer bin phpunit require --dev -W phpunit/phpunit:@dev --ignore-platform-reqs
rm -rf ./vendor/phpunit/
composer dump-autoload
fi
- name: Init DB and WP
working-directory: plugins/woocommerce
run: ./tests/bin/install.sh woo_test root root 127.0.0.1 ${{ matrix.wp }}
- name: Init DB and WP
working-directory: plugins/woocommerce
run: ./tests/bin/install.sh woo_test root root 127.0.0.1 ${{ matrix.wp }}
- name: Run tests
working-directory: plugins/woocommerce
run: pnpm run test --filter=woocommerce --color
- name: Run tests
working-directory: plugins/woocommerce
run: pnpm run test --filter=woocommerce --color

View File

@ -14,8 +14,8 @@ jobs:
name: Run prepare script
runs-on: ubuntu-20.04
permissions:
contents: read
pull-requests: write
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v3

View File

@ -1,43 +1,47 @@
name: "Pull request post-merge processing"
name: 'Pull request post-merge processing'
on:
pull_request_target:
types: [closed]
pull_request_target:
types: [closed]
paths:
- 'packages/**'
- 'plugins/woocommerce/**'
- 'plugins/woocommerce-admin/**'
permissions: {}
jobs:
process-pull-request-after-merge:
name: "Process a pull request after it's merged"
if: github.event.pull_request.merged == true
runs-on: ubuntu-20.04
permissions:
pull-requests: write
steps:
- name: "Get the action scripts"
run: |
scripts="assign-milestone-to-merged-pr.php add-post-merge-comment.php post-request-shared.php"
for script in $scripts
do
curl \
--silent \
--fail \
--header 'Authorization: bearer ${{ secrets.GITHUB_TOKEN }}' \
--header 'User-Agent: GitHub action to set the milestone for a pull request' \
--header 'Accept: application/vnd.github.v3.raw' \
--output $script \
--location "$GITHUB_API_URL/repos/${{ github.repository }}/contents/.github/workflows/scripts/$script?ref=${{ github.event.pull_request.base.ref }}"
done
env:
GITHUB_API_URL: ${{ env.GITHUB_API_URL }}
- name: "Install PHP"
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
- name: "Run the script to assign a milestone"
if: |
!github.event.pull_request.milestone &&
github.event.pull_request.base.ref == 'trunk'
run: php assign-milestone-to-merged-pr.php
env:
PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
process-pull-request-after-merge:
name: "Process a pull request after it's merged"
if: github.event.pull_request.merged == true
runs-on: ubuntu-20.04
permissions:
pull-requests: write
steps:
- name: 'Get the action scripts'
run: |
scripts="assign-milestone-to-merged-pr.php add-post-merge-comment.php post-request-shared.php"
for script in $scripts
do
curl \
--silent \
--fail \
--header 'Authorization: bearer ${{ secrets.GITHUB_TOKEN }}' \
--header 'User-Agent: GitHub action to set the milestone for a pull request' \
--header 'Accept: application/vnd.github.v3.raw' \
--output $script \
--location "$GITHUB_API_URL/repos/${{ github.repository }}/contents/.github/workflows/scripts/$script?ref=${{ github.event.pull_request.base.ref }}"
done
env:
GITHUB_API_URL: ${{ env.GITHUB_API_URL }}
- name: 'Install PHP'
uses: shivammathur/setup-php@8e2ac35f639d3e794c1da1f28999385ab6fdf0fc
with:
php-version: '7.4'
- name: 'Run the script to assign a milestone'
if: |
!github.event.pull_request.milestone &&
github.event.pull_request.base.ref == 'trunk'
run: php assign-milestone-to-merged-pr.php
env:
PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -21,8 +21,8 @@ jobs:
create-changelog-prs:
runs-on: ubuntu-20.04
permissions:
contents: read
pull-requests: write
contents: write
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v3
@ -46,6 +46,9 @@ jobs:
- name: 'Generate the changelog file'
run: pnpm --filter=woocommerce run changelog write --add-pr-num -n -vvv --use-version ${{ inputs.releaseVersion }}
- name: Checkout pnpm-lock.yaml to prevent issues
run: git checkout pnpm-lock.yaml
- name: 'git rm deleted files'
run: git rm $(git ls-files --deleted)

View File

@ -30,7 +30,7 @@ jobs:
freeze: ${{ steps.check-freeze.outputs.freeze }}
steps:
- name: 'Install PHP'
uses: shivammathur/setup-php@v2
uses: shivammathur/setup-php@8e2ac35f639d3e794c1da1f28999385ab6fdf0fc
with:
php-version: '7.4'
@ -60,7 +60,8 @@ jobs:
name: 'Maybe create next milestone and release branch'
runs-on: ubuntu-20.04
permissions:
contents: read
contents: write
issues: write
needs: verify-code-freeze
if: needs.verify-code-freeze.outputs.freeze == 0
outputs:
@ -89,8 +90,8 @@ jobs:
name: Preps trunk for next development cycle
runs-on: ubuntu-20.04
permissions:
contents: read
pull-requests: write
contents: write
pull-requests: write
needs: maybe-create-next-milestone-and-release-branch
steps:
- name: Checkout code
@ -159,7 +160,7 @@ jobs:
name: 'Trigger changelog action'
runs-on: ubuntu-20.04
permissions:
actions: write
actions: write
needs: maybe-create-next-milestone-and-release-branch
steps:
- name: 'Trigger changelog action'

View File

@ -0,0 +1,34 @@
name: WooCommerce Beta Tester Release
permissions: {}
on:
workflow_dispatch:
inputs:
version:
description: 'The version number for the release'
required: true
jobs:
release:
name: Run release scripts
runs-on: ubuntu-20.04
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
- name: Build WooCommerce Beta Tester Zip
working-directory: plugins/woocommerce-beta-tester
run: pnpm build:zip
- name: Create release
id: create_release
uses: woocommerce/action-gh-release@master
with:
tag_name: wc-beta-tester-${{ inputs.version }}
name: WooCommerce Beta Tester Release ${{ inputs.version }}
draft: false
files: plugins/woocommerce-beta-tester/woocommerce-beta-tester.zip

View File

@ -10,8 +10,8 @@ if ( getenv( 'TIME_OVERRIDE' ) ) {
$base_dir = dirname( dirname( dirname( __DIR__ ) ) );
// The release date is 26 days after the code freeze.
$release_time = strtotime( '+26 days', $now );
// The release date is 22 days after the code freeze.
$release_time = strtotime( '+22 days', $now );
$release_date = date( 'Y-m-d', $release_time );
$readme_file = $base_dir . '/plugins/woocommerce/readme.txt';

View File

@ -17,11 +17,56 @@ concurrency:
permissions: {}
jobs:
api-tests:
name: API tests on nightly build
runs-on: ubuntu-20.04
permissions:
contents: read
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report
steps:
- uses: actions/checkout@v3
with:
ref: ${{ env.BRANCH_NAME }}
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install-filters: woocommerce
build: false
- name: Run API tests.
working-directory: plugins/woocommerce
env:
BASE_URL: ${{ secrets.SMOKE_TEST_URL }}
USER_KEY: ${{ secrets.SMOKE_TEST_ADMIN_USER }}
USER_SECRET: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello.test.js
- name: Generate API Test report.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive API test report
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ${{ env.API_ARTIFACT }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
e2e-tests:
name: E2E tests on nightly build
runs-on: ubuntu-20.04
permissions:
contents: read
needs: [api-tests]
env:
ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }}
ADMIN_USER: ${{ secrets.SMOKE_TEST_ADMIN_USER }}
@ -78,52 +123,6 @@ jobs:
if-no-files-found: ignore
retention-days: 5
api-tests:
name: API tests on nightly build
runs-on: ubuntu-20.04
permissions:
contents: read
needs: [e2e-tests]
if: success() || failure()
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report
steps:
- uses: actions/checkout@v3
with:
ref: ${{ env.BRANCH_NAME }}
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install-filters: woocommerce
build: false
- name: Run API tests.
working-directory: plugins/woocommerce
env:
BASE_URL: ${{ secrets.SMOKE_TEST_URL }}
USER_KEY: ${{ secrets.SMOKE_TEST_ADMIN_USER }}
USER_SECRET: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello.test.js
- name: Generate API Test report.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive API test report
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ${{ env.API_ARTIFACT }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
k6-tests:
name: k6 tests on nightly build
runs-on: ubuntu-20.04
@ -181,6 +180,7 @@ jobs:
runs-on: ubuntu-20.04
permissions:
contents: read
needs: [api-tests]
env:
USE_WP_ENV: 1
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results
@ -256,7 +256,7 @@ jobs:
runs-on: ubuntu-20.04
permissions:
contents: read
needs: [test-plugins, k6-tests]
needs: [e2e-tests, test-plugins, k6-tests]
steps:
- name: Create dirs
run: |
@ -312,7 +312,7 @@ jobs:
( success() || failure() ) &&
! github.event.pull_request.head.repo.fork
runs-on: ubuntu-20.04
needs: [test-plugins, k6-tests]
needs: [e2e-tests, test-plugins, k6-tests]
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
RUN_ID: ${{ github.run_id }}

View File

@ -1,37 +1,37 @@
name: Synchronize Dependencies with syncpack
on:
# Run whenever a pull request is updated
pull_request:
branches:
- trunk
paths:
- '**/package.json'
# Run whenever a pull request is updated
pull_request:
branches:
- trunk
paths:
- '**/package.json'
permissions: {}
jobs:
syncpack:
runs-on: ubuntu-latest
permissions:
contents: read
name: syncpack
steps:
- name: 'Checkout'
uses: actions/checkout@v3
- name: 'Setup node'
uses: actions/setup-node@v3
with:
node-version: 16
syncpack:
runs-on: ubuntu-latest
permissions:
contents: read
name: syncpack
steps:
- name: 'Checkout'
uses: actions/checkout@v3
- name: 'Install Syncpack'
run: npm install -g syncpack@^8.2.4
- name: 'List Mismatches'
run: syncpack list-mismatches
- name: 'Explain Remedy'
if: failure()
run: |
echo "Dependency version mismatch detected. This can usually be fixed automatically by updating the pinned version in \`.syncpackrc\` and then running: \`pnpm run sync-dependencies\`"
exit 1
- name: 'Setup node'
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
with:
node-version: 16
- name: 'Install Syncpack'
run: npm install -g syncpack@^8.2.4
- name: 'List Mismatches'
run: syncpack list-mismatches
- name: 'Explain Remedy'
if: failure()
run: |
echo "Dependency version mismatch detected. This can usually be fixed automatically by updating the pinned version in \`.syncpackrc\` and then running: \`pnpm run sync-dependencies\`"
exit 1

4
.husky/post-checkout Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm install

View File

@ -1,6 +1,6 @@
{
"dev": true,
"filter": "^(?:react|react-dom|typescript|@typescript-eslint|@types/react).*$",
"filter": "^(?:config|react|react-dom|eslint|typescript|@typescript-eslint|@types/react).*$",
"indent": "\t",
"overrides": true,
"peer": true,
@ -33,6 +33,15 @@
"**"
]
},
{
"dependencies": [
"config"
],
"packages": [
"**"
],
"pinVersion": "3.3.7"
},
{
"dependencies": [
"react",
@ -51,6 +60,18 @@
"**"
],
"pinVersion": "^4.8.3"
},
{
"dependencies": [
"eslint"
],
"dependencyTypes": [
"devDependencies"
],
"packages": [
"**"
],
"pinVersion": "^8.32.0"
}
]
}

View File

@ -31,8 +31,9 @@ if [ $? -ne 0 ]; then
exit 1
fi
# Ensure both branches are tracked or check-changelogger-use will fail.
git checkout $PROTECTED_BRANCH --quiet
git checkout $CURRENT_BRANCH --quiet
# Ensure both branches are tracked or check-changelogger-use will fail. Note we pass hooksPath
# to avoid running the pre-commit hook.
git -c core.hooksPath=/dev/null checkout $PROTECTED_BRANCH --quiet
git -c core.hooksPath=/dev/null checkout $CURRENT_BRANCH --quiet
php tools/monorepo/check-changelogger-use.php $PROTECTED_BRANCH $CURRENT_BRANCH

View File

@ -1,5 +1,235 @@
== Changelog ==
= 7.4.0 2023-02-14 =
**WooCommerce**
* Fix - Add support for sorting by includes param. [#36215](https://github.com/woocommerce/woocommerce/pull/36215)
* Fix - Allow product tab navigation without prompting for unsaved changes [#36235](https://github.com/woocommerce/woocommerce/pull/36235)
* Fix - Convert HTML to blocks in product variation description [#36241](https://github.com/woocommerce/woocommerce/pull/36241)
* Fix - Decode HTML entities in CategoryBreadcrumbs. [#36321](https://github.com/woocommerce/woocommerce/pull/36321)
* Fix - Decode HTML entities in CategoryFieldItem. [#36367](https://github.com/woocommerce/woocommerce/pull/36367)
* Fix - Ensure order emails are responsive in most email clients, including when the current language is RTL. [#36310](https://github.com/woocommerce/woocommerce/pull/36310)
* Fix - Ensures product variation sort order is correctly persisted. [#36343](https://github.com/woocommerce/woocommerce/pull/36343)
* Fix - Ensure wc_get_order() works without arguments when HPOS is enabled. [#36496](https://github.com/woocommerce/woocommerce/pull/36496)
* Fix - Fix "Save changes?" modal saves the options after selecting the 'Discard' option [#36160](https://github.com/woocommerce/woocommerce/pull/36160)
* Fix - Fix attributes/options lists corrupt render #36236 [#36236](https://github.com/woocommerce/woocommerce/pull/36236)
* Fix - Fix bug when filtering for customer_id=0. [#36216](https://github.com/woocommerce/woocommerce/pull/36216)
* Fix - Fix deprecated usage of ${var} in strings [#36439](https://github.com/woocommerce/woocommerce/pull/36439)
* Fix - Fix edit attribute modal terms list [#36186](https://github.com/woocommerce/woocommerce/pull/36186)
* Fix - Fixes editing of child product reviews. [#35888](https://github.com/woocommerce/woocommerce/pull/35888)
* Fix - Fix for product filters when 'shop' page is the front page. [#36224](https://github.com/woocommerce/woocommerce/pull/36224)
* Fix - Fix issue where attribute term dropdown was not adhering to sort order setting. [#36047](https://github.com/woocommerce/woocommerce/pull/36047)
* Fix - Fix navigation between variations and tab selection [#36239](https://github.com/woocommerce/woocommerce/pull/36239)
* Fix - Fix notices styling in Twenty Twenty-Three [#36475](https://github.com/woocommerce/woocommerce/pull/36475)
* Fix - Fix overlapping header elements on product page [#36495](https://github.com/woocommerce/woocommerce/pull/36495)
* Fix - Fix product table dropdown issue on mobile. [#36046](https://github.com/woocommerce/woocommerce/pull/36046)
* Fix - Fix reordering list items error [#36296](https://github.com/woocommerce/woocommerce/pull/36296)
* Fix - Fix REST API order refunds enpoint when HPOS is active, and make v2 orders endpoint compatible with HPOS [#36308](https://github.com/woocommerce/woocommerce/pull/36308)
* Fix - Fix settings tables styles [#36531](https://github.com/woocommerce/woocommerce/pull/36531)
* Fix - Fix tax task showing as not completed after setting up tax [#36468](https://github.com/woocommerce/woocommerce/pull/36468)
* Fix - Fix the signature mismatch affecting wc cli commands ability to fetch user subscription data. [#36240](https://github.com/woocommerce/woocommerce/pull/36240)
* Fix - Fix total count query of orders within Analytics reports data store. [#35971](https://github.com/woocommerce/woocommerce/pull/35971)
* Fix - Hide Variations section when it is empty [#36202](https://github.com/woocommerce/woocommerce/pull/36202)
* Fix - Improve accessibility of the coupon code label, in the context of the cart page. [#36247](https://github.com/woocommerce/woocommerce/pull/36247)
* Fix - Improve the way we retrieve the alt text property for product attachments. [#35009](https://github.com/woocommerce/woocommerce/pull/35009)
* Fix - Load wc_empty_cart function for REST API calls. [#36182](https://github.com/woocommerce/woocommerce/pull/36182)
* Fix - Make HPOS UX more consistent with posts UI (so that same e2e tests passes for both). [#36282](https://github.com/woocommerce/woocommerce/pull/36282)
* Fix - Make order edit messages compatible with both posts and theorder object. [#36485](https://github.com/woocommerce/woocommerce/pull/36485)
* Fix - Make sure the tracking shortcode only operates in orders with billing information. [#33735](https://github.com/woocommerce/woocommerce/pull/33735)
* Fix - Remove persisted query on return to parent product from variation [#36365](https://github.com/woocommerce/woocommerce/pull/36365)
* Fix - Reset variation form if a new variation is given [#36078](https://github.com/woocommerce/woocommerce/pull/36078)
* Fix - Restore the pre-7.2.0 behavior for single product quantity inputs. [#36460](https://github.com/woocommerce/woocommerce/pull/36460)
* Fix - Set child orders to be children of current order parent before deleting for consistency. [#36218](https://github.com/woocommerce/woocommerce/pull/36218)
* Fix - Skip custom search for HPOS API queries as it's handled already. [#36213](https://github.com/woocommerce/woocommerce/pull/36213)
* Fix - Use Imagick functions to set parallel thread count instead of direct putenv call as suggested in https://core.trac.wordpress.org/ticket/36534#comment:129. [#35339](https://github.com/woocommerce/woocommerce/pull/35339)
* Fix - When adjusting download permissions, confirm the child products have not been removed. [#36431](https://github.com/woocommerce/woocommerce/pull/36431)
* Add - Add ability to filter variations by local attributes in REST API [#36201](https://github.com/woocommerce/woocommerce/pull/36201)
* Add - Add an admin notice about the upcoming PHP version requirement change for PHP 7.2 users [#36444](https://github.com/woocommerce/woocommerce/pull/36444)
* Add - Added a slot for extending the app with a homescreen header banner [#36467](https://github.com/woocommerce/woocommerce/pull/36467)
* Add - Added a slot for ProgressHeader and ProgressTitle component [#36482](https://github.com/woocommerce/woocommerce/pull/36482)
* Add - Add edit button to variations list items [#36079](https://github.com/woocommerce/woocommerce/pull/36079)
* Add - Added slot for tasklist completion slotfill [#36487](https://github.com/woocommerce/woocommerce/pull/36487)
* Add - Add endpoint to create all product variations [#35980](https://github.com/woocommerce/woocommerce/pull/35980)
* Add - Add exit prompt CES for users editing orders when tracking is enabled. [#35762](https://github.com/woocommerce/woocommerce/pull/35762)
* Add - Adding delayed spotlight to feedback button on current product page. [#35865](https://github.com/woocommerce/woocommerce/pull/35865)
* Add - Adding feedback button to activity bar on classic product page. [#35810](https://github.com/woocommerce/woocommerce/pull/35810)
* Add - Adding JS data store for ProductForm. [#36430](https://github.com/woocommerce/woocommerce/pull/36430)
* Add - Adding the WooProductSectionItem slot within the product editor general tab. [#36331](https://github.com/woocommerce/woocommerce/pull/36331)
* Add - Add initial product form PHP helper class to add new fields. [#36093](https://github.com/woocommerce/woocommerce/pull/36093)
* Add - Additional error logging within the CSV Exporter framework. [#34802](https://github.com/woocommerce/woocommerce/pull/34802)
* Add - Add multichannel marketing API [#36453](https://github.com/woocommerce/woocommerce/pull/36453)
* Add - Add new filter to add additional clauses for SQL statement in Variations report [#36378](https://github.com/woocommerce/woocommerce/pull/36378)
* Add - Add new product form API for extending the new Product Form MVP. [#36165](https://github.com/woocommerce/woocommerce/pull/36165)
* Add - Add Options section to new product experience form. [#35910](https://github.com/woocommerce/woocommerce/pull/35910)
* Add - Add product tour to new product management experience [#36428](https://github.com/woocommerce/woocommerce/pull/36428)
* Add - Add product variation form [#36033](https://github.com/woocommerce/woocommerce/pull/36033)
* Add - Add product variation General section [#36081](https://github.com/woocommerce/woocommerce/pull/36081)
* Add - Add product variation header actions and persistence [#36155](https://github.com/woocommerce/woocommerce/pull/36155)
* Add - Add product variation image [#36133](https://github.com/woocommerce/woocommerce/pull/36133)
* Add - Add product variation navigation component [#36076](https://github.com/woocommerce/woocommerce/pull/36076)
* Add - Add product variations flag to only show work in development [#36311](https://github.com/woocommerce/woocommerce/pull/36311)
* Add - Add product variation title to page header [#36085](https://github.com/woocommerce/woocommerce/pull/36085)
* Add - Add Product variation visibility toggle [#36020](https://github.com/woocommerce/woocommerce/pull/36020)
* Add - Add single product variation sections [#36051](https://github.com/woocommerce/woocommerce/pull/36051)
* Add - Adds support for a 'required' argument when invoking `wc_dropdown_variation_attribute_options()`. [#34579](https://github.com/woocommerce/woocommerce/pull/34579)
* Add - Add support for sorting by order metadata in HPOS queries. [#36403](https://github.com/woocommerce/woocommerce/pull/36403)
* Add - Add WooOnboardingTaskListHeader, woocommerce_admin_experimental_onboarding_tasklists filter, and woocommerce_onboarding_task_list_header Slot to task list [#36519](https://github.com/woocommerce/woocommerce/pull/36519)
* Add - Include tax options in pricing section [#36299](https://github.com/woocommerce/woocommerce/pull/36299)
* Add - Persist active tab on refresh [#36112](https://github.com/woocommerce/woocommerce/pull/36112)
* Add - Persist variations order on product save [#36109](https://github.com/woocommerce/woocommerce/pull/36109)
* Add - Product variation quantity status indicator [#35982](https://github.com/woocommerce/woocommerce/pull/35982)
* Add - Product variations card should have a fixed height. [#36053](https://github.com/woocommerce/woocommerce/pull/36053)
* Add - Remove manage_stock 'parent' value before saving the variation [#36234](https://github.com/woocommerce/woocommerce/pull/36234)
* Add - Run ces exit prompt when product import abandoned. [#35996](https://github.com/woocommerce/woocommerce/pull/35996)
* Add - Scroll newly added product attribute into view in new product management experience [#36447](https://github.com/woocommerce/woocommerce/pull/36447)
* Add - Show product CES footer on product tour close [#36516](https://github.com/woocommerce/woocommerce/pull/36516)
* Add - Truncate attribute option name to a max of 32 chars in variations list [#36134](https://github.com/woocommerce/woocommerce/pull/36134)
* Add - Trying experimental slot context with product editor fills. [#36333](https://github.com/woocommerce/woocommerce/pull/36333)
* Add - Using slotfill to insert attributes section in the product editor. [#36483](https://github.com/woocommerce/woocommerce/pull/36483)
* Add - Using slotfill to insert images section in product editor. [#36461](https://github.com/woocommerce/woocommerce/pull/36461)
* Update - Update woocommerce-blocks to 9.4.3. [#36736](https://github.com/woocommerce/woocommerce/pull/36736)
* Update - Adding WooProductFieldItem slot to product details section. [#36315](https://github.com/woocommerce/woocommerce/pull/36315)
* Update - Add permalink_template and generated_slug to products REST API response. [#36497](https://github.com/woocommerce/woocommerce/pull/36497)
* Update - Auto generate variations on option changes [#36188](https://github.com/woocommerce/woocommerce/pull/36188)
* Update - Bundled version of Action Scheduler updated to 3.5.4. [#36433](https://github.com/woocommerce/woocommerce/pull/36433)
* Update - Customers REST API endpoint will now return user metadata only when requester has an administrator role [#36408](https://github.com/woocommerce/woocommerce/pull/36408)
* Update - Disable irrelevant product tabs when variations exist [#35939](https://github.com/woocommerce/woocommerce/pull/35939)
* Update - Migrate shipping section in product editor to slot fill. [#36534](https://github.com/woocommerce/woocommerce/pull/36534)
* Update - Move product management feature flag down to experimental. [#36552](https://github.com/woocommerce/woocommerce/pull/36552)
* Update - Reimplementing product details fields in product editor as slot fills. [#36368](https://github.com/woocommerce/woocommerce/pull/36368)
* Update - Update api-core-tests readme to include a guide for writing tests [#35978](https://github.com/woocommerce/woocommerce/pull/35978)
* Update - Update store-details test snapshot to reflect updated select-control [#35808](https://github.com/woocommerce/woocommerce/pull/35808)
* Update - Update WooCommerce Blocks to 9.4.0 [#36524](https://github.com/woocommerce/woocommerce/pull/36524)
* Update - Update WooCommerce Blocks to 9.4.1 [#36553](https://github.com/woocommerce/woocommerce/pull/36553)
* Update - Update WooCommerce Blocks to 9.4.2 [#36624](https://github.com/woocommerce/woocommerce/pull/36624)
* Dev - Add advanced setting option [#36380](https://github.com/woocommerce/woocommerce/pull/36380)
* Dev - Add experimental SlotFill for task list footer [#36527](https://github.com/woocommerce/woocommerce/pull/36527)
* Dev - Cleanup product task experiment [#35950](https://github.com/woocommerce/woocommerce/pull/35950)
* Dev - Consistent folder structure for E2E and API test results [#35907](https://github.com/woocommerce/woocommerce/pull/35907)
* Dev - Fix docblock type annotations for $meta_value. [#33853](https://github.com/woocommerce/woocommerce/pull/33853)
* Dev - Fix flakiness of the `can save industry changes when navigating back to "Store Details"` E2E test. [#36260](https://github.com/woocommerce/woocommerce/pull/36260)
* Dev - Make shopper tests passable on daily smoke test site. [#35873](https://github.com/woocommerce/woocommerce/pull/35873)
* Dev - Move product attribute fetching logic into a separate hook [#36354](https://github.com/woocommerce/woocommerce/pull/36354)
* Dev - Update TaskLists::add_task() to reflect changes in TaskList::add_task() [#36104](https://github.com/woocommerce/woocommerce/pull/36104)
* Dev - Update the browserslist config for legacy client JS to match Wordpress. [#36264](https://github.com/woocommerce/woocommerce/pull/36264)
* Dev - Upgrade PHPUnit to v8 [#36273](https://github.com/woocommerce/woocommerce/pull/36273)
* Tweak - Corrects a typo in the i18n/states.php file, relating to our list of Iranian states. [#36457](https://github.com/woocommerce/woocommerce/pull/36457)
* Tweak - Derive product type from product attributes [#36243](https://github.com/woocommerce/woocommerce/pull/36243)
* Tweak - Fix typo in a function comment. [#36122](https://github.com/woocommerce/woocommerce/pull/36122)
* Tweak - Fix units in function doc comment [#36353](https://github.com/woocommerce/woocommerce/pull/36353)
* Tweak - Make related products check more robust against wrong transients. [#34742](https://github.com/woocommerce/woocommerce/pull/34742)
* Tweak - Makes it possible to use an `add_meta_boxes_<SCREEN_ID>` style hook in the HPOS editor, for parity with the traditional post editor. [#35999](https://github.com/woocommerce/woocommerce/pull/35999)
* Tweak - Minor adjustments to the ProductForm API [#36414](https://github.com/woocommerce/woocommerce/pull/36414)
* Tweak - Redirect to new product experience when in experiment group [#36381](https://github.com/woocommerce/woocommerce/pull/36381)
* Tweak - Refactor AttributeField into sub-components. [#35997](https://github.com/woocommerce/woocommerce/pull/35997)
* Tweak - Update product links when new product management experience is enabled [#36382](https://github.com/woocommerce/woocommerce/pull/36382)
* Tweak - Updates and improves the docblocks for methods WC_Order::get_total() and WC_Order::get_subtotal(). [#34385](https://github.com/woocommerce/woocommerce/pull/34385)
* Tweak - Validation of Norweigan postcodes has been added. [#36277](https://github.com/woocommerce/woocommerce/pull/36277)
* Performance - Speed up HPOS search query by using group by instead of distinct. [#35897](https://github.com/woocommerce/woocommerce/pull/35897)
* Enhancement - Add context to countries shipping to prefix [#36254](https://github.com/woocommerce/woocommerce/pull/36254)
* Enhancement - Adds new order status filters for bacs and cheque email instructions. [#35849](https://github.com/woocommerce/woocommerce/pull/35849)
* Enhancement - Improves handling of the single product page quantity selector, in relation to variable products. [#36087](https://github.com/woocommerce/woocommerce/pull/36087)
* Enhancement - Remove default WooCommerce button styles if using a block theme which adds button styles in theme.json [#36225](https://github.com/woocommerce/woocommerce/pull/36225)
= 7.3.0 2023-01-10 =
**WooCommerce**
* Fix - Remove redundant Pinterest plugin from marketing task [#36158](https://github.com/woocommerce/woocommerce/pull/36158)
* Fix - Corrects a hard-coded reference to the WP post meta table within the HPOS Migration Helper, that would fail on some sites. [#36100](https://github.com/woocommerce/woocommerce/pull/36100)
* Fix - Add a blank space between the emoji and the message within a notice popup [#35698](https://github.com/woocommerce/woocommerce/pull/35698)
* Fix - Add a data migration for changed New Zealand and Ukraine state codes [#35960](https://github.com/woocommerce/woocommerce/pull/35960)
* Fix - Add back missing scss files from assets. [#35624](https://github.com/woocommerce/woocommerce/pull/35624)
* Fix - Address HPOS synchronization issues relating to the deletion of orders. [#35723](https://github.com/woocommerce/woocommerce/pull/35723)
* Fix - Avoid a potential fatal error when forming edit-order URLs. [#35995](https://github.com/woocommerce/woocommerce/pull/35995)
* Fix - Fix call of array_key_exists in SSR. [#35598](https://github.com/woocommerce/woocommerce/pull/35598)
* Fix - Fix ellipsis dropdown menu is mostly hidden within the task list [#35949](https://github.com/woocommerce/woocommerce/pull/35949)
* Fix - Fixes fatal error resulting from translating the WooCommerce main menu. [#35695](https://github.com/woocommerce/woocommerce/pull/35695)
* Fix - Fix get orders REST API endpoint when using 'search' or 'parent' and HPOS is enabled [#35818](https://github.com/woocommerce/woocommerce/pull/35818)
* Fix - Fix handling of non-ASCII product attributes when the attributes lookup table is in use [#34432](https://github.com/woocommerce/woocommerce/pull/34432)
* Fix - Fix handling of statuses in orders list table (HPOS). [#35370](https://github.com/woocommerce/woocommerce/pull/35370)
* Fix - Fix logo icon for Google Listings and Ads. [#35732](https://github.com/woocommerce/woocommerce/pull/35732)
* Fix - Fix product tab to be shown on production build [#35976](https://github.com/woocommerce/woocommerce/pull/35976)
* Fix - Fix regexp used for filtering country dropdown on the store details step #35941 [#35942](https://github.com/woocommerce/woocommerce/pull/35942)
* Fix - Fix the gap in the featured product checkbox [#35710](https://github.com/woocommerce/woocommerce/pull/35710)
* Fix - Fix tooltips not appearing in the orders list admin page. [#35638](https://github.com/woocommerce/woocommerce/pull/35638)
* Fix - Fix unread note count on inbox panel [#35396](https://github.com/woocommerce/woocommerce/pull/35396)
* Fix - Fix unsaved modal propmt to not be shown during form submission [#35657](https://github.com/woocommerce/woocommerce/pull/35657)
* Fix - Fix version in template and function docblocks. [#35473](https://github.com/woocommerce/woocommerce/pull/35473)
* Fix - Fix WooCommerce Admin client React build warnings and remove unnecessary scss imports. [#35930](https://github.com/woocommerce/woocommerce/pull/35930)
* Fix - Fix wrong query param in onboarding product api call [#35926](https://github.com/woocommerce/woocommerce/pull/35926)
* Fix - If order types have not been registered, do not attempt to count orders. [#35820](https://github.com/woocommerce/woocommerce/pull/35820)
* Fix - Make the 'unprotected upload directory' notice dismissable. [#33544](https://github.com/woocommerce/woocommerce/pull/33544)
* Fix - Update Playwright to 1.28.0 and explicitly set PHP version in GH action [#35679](https://github.com/woocommerce/woocommerce/pull/35679)
* Fix - When importing product CSV, ensure line breaks within header columns do not break the import process. [#35880](https://github.com/woocommerce/woocommerce/pull/35880)
* Add - Add CES exit prompt for setting pages, when tracking is enabled. [#35761](https://github.com/woocommerce/woocommerce/pull/35761)
* Add - Add CES feedback functionality to the share feedback button within the Product MVP. [#35690](https://github.com/woocommerce/woocommerce/pull/35690)
* Add - Add Denmark postcode validation. [#35653](https://github.com/woocommerce/woocommerce/pull/35653)
* Add - Add exit prompt logic to get feedback if users leave product pages without saving when tracking is enabled. [#35728](https://github.com/woocommerce/woocommerce/pull/35728)
* Add - Add FormFileUpload component [#35358](https://github.com/woocommerce/woocommerce/pull/35358)
* Add - Add HPOS information to WC Tracker. [#35446](https://github.com/woocommerce/woocommerce/pull/35446)
* Add - Add new option to create new attribute within add attribute modal. [#35100](https://github.com/woocommerce/woocommerce/pull/35100)
* Add - Add new product management breadcrumbs to header [#35596](https://github.com/woocommerce/woocommerce/pull/35596)
* Add - Add new Product MVP CES footer for gathering feedback on the new product management screen. [#35652](https://github.com/woocommerce/woocommerce/pull/35652)
* Add - Add one-click installation to recommended extensions in Marketing page. [#35542](https://github.com/woocommerce/woocommerce/pull/35542)
* Add - Add pagination to variations list [#35979](https://github.com/woocommerce/woocommerce/pull/35979)
* Add - Add product settings menu in header [#35592](https://github.com/woocommerce/woocommerce/pull/35592)
* Add - Add product tab headers and move sections to respective tabs [#35862](https://github.com/woocommerce/woocommerce/pull/35862)
* Add - Add product variations list to new product management experience [#35889](https://github.com/woocommerce/woocommerce/pull/35889)
* Add - Add support for custom order types in HPOS admin UI. [#35658](https://github.com/woocommerce/woocommerce/pull/35658)
* Add - Add the woocommerce_order_applied_coupon hook [#35616](https://github.com/woocommerce/woocommerce/pull/35616)
* Add - Add tracks events for 'product_view_product_click' and 'product_view_product_dismiss' [#35582](https://github.com/woocommerce/woocommerce/pull/35582)
* Add - Introduces action `woocommerce_order_list_table_restrict_manage_orders` as an equivalent of the legacy `restrict_manage_posts` hook. [#36000](https://github.com/woocommerce/woocommerce/pull/36000)
* Add - Open categories menu list when the user focus the category field [#35606](https://github.com/woocommerce/woocommerce/pull/35606)
* Update - Match country name or ' - region' when filtering country select control #36120 [#36159](https://github.com/woocommerce/woocommerce/pull/36159)
* Update - Update WooCommerce Blocks to 9.1.3 [#36125](https://github.com/woocommerce/woocommerce/pull/36125)
* Update - Adapt the summary text in the product management form. [#35717](https://github.com/woocommerce/woocommerce/pull/35717)
* Update - Add Codisto for WooCommerce to free extensions list [#36009](https://github.com/woocommerce/woocommerce/pull/36009)
* Update - Add experimental open menu on focus option to the attribute and attribute term input fields. [#35758](https://github.com/woocommerce/woocommerce/pull/35758)
* Update - Add missing tracks events [#35262](https://github.com/woocommerce/woocommerce/pull/35262)
* Update - Add Pinterest for WooCommerce to free extensions list [#36003](https://github.com/woocommerce/woocommerce/pull/36003)
* Update - Automatically create the custom order tables if the corresponding feature is enabled [#35357](https://github.com/woocommerce/woocommerce/pull/35357)
* Update - Disable TikTok in the OBW [#35924](https://github.com/woocommerce/woocommerce/pull/35924)
* Update - Include taxes migration in MigrationHelper::migrate_country_states [#35967](https://github.com/woocommerce/woocommerce/pull/35967)
* Update - Increase consistency in relation to the way taxonomy term ordering is persisted. [#34645](https://github.com/woocommerce/woocommerce/pull/34645)
* Update - Make product form header and actions responsive for smaller viewports [#35623](https://github.com/woocommerce/woocommerce/pull/35623)
* Update - Remove welcome to woocommerce store note [#35342](https://github.com/woocommerce/woocommerce/pull/35342)
* Update - Surface Amazon Pay as "Additional Payment Options" for UK/EU/JP [#35726](https://github.com/woocommerce/woocommerce/pull/35726)
* Update - Update api-core-tests readme to reference correct directory for .env file [#35759](https://github.com/woocommerce/woocommerce/pull/35759)
* Update - Update country data in api-core-tests to prevent numerous test data updates [#35557](https://github.com/woocommerce/woocommerce/pull/35557)
* Update - update FAQ in readme consumed by .org [#35696](https://github.com/woocommerce/woocommerce/pull/35696)
* Update - Update WooCommerce Blocks to 9.1.1 [#36004](https://github.com/woocommerce/woocommerce/pull/36004)
* Update - Update wording for In-App Marketplace tour. [#35929](https://github.com/woocommerce/woocommerce/pull/35929)
* Update - Updating all CES events to support two questions in modal. [#35680](https://github.com/woocommerce/woocommerce/pull/35680)
* Dev - Allow the user to select multiple images in the Media Library [#35722](https://github.com/woocommerce/woocommerce/pull/35722)
* Dev - Check if blocks have been added to rich text editors before updating value [#35626](https://github.com/woocommerce/woocommerce/pull/35626)
* Dev - Make e2e tests compatible with nightly and release smoke test sites. [#35492](https://github.com/woocommerce/woocommerce/pull/35492)
* Dev - Move file picker by clicking card into the MediaUploader component [#35738](https://github.com/woocommerce/woocommerce/pull/35738)
* Dev - Update the "can manually add a variation" E2E test to prevent automatic creation of variations from all attributes. [#36008](https://github.com/woocommerce/woocommerce/pull/36008)
* Tweak - Avoid deprecation notices under PHP 8.1 when calling wp_parse_url(). [#35648](https://github.com/woocommerce/woocommerce/pull/35648)
* Tweak - Correct the usage of 'address' and 'addresses' within `wc_get_account_menu_items()`. [#32026](https://github.com/woocommerce/woocommerce/pull/32026)
* Tweak - Create ProductForm component to merge similar structures between AddProductPage and EditProductPage [#35783](https://github.com/woocommerce/woocommerce/pull/35783)
* Tweak - Improves efficiency of code responsible for determining plugin IDs (during feature compatibility checks). [#35727](https://github.com/woocommerce/woocommerce/pull/35727)
* Tweak - Make the formatted shipping address available via the `woocommerce_cart_no_shipping_available_html` hook. [#30723](https://github.com/woocommerce/woocommerce/pull/30723)
* Tweak - Make the OrdersTableDataStore init_order_record() and get_order_data_for_ids() functions protected rather than private [#35829](https://github.com/woocommerce/woocommerce/pull/35829)
* Tweak - Move CSS about notice outside of .woocommerce class scope [#35912](https://github.com/woocommerce/woocommerce/pull/35912)
* Tweak - Resolve an error in the product tracking code by testing to see if the `post_type` query var is set before checking its value. [#34501](https://github.com/woocommerce/woocommerce/pull/34501)
* Tweak - Simplify wording within the customer emails for on-hold orders. [#31886](https://github.com/woocommerce/woocommerce/pull/31886)
* Tweak - WooCommerce has now been tested up to WordPress 6.1.x. [#35985](https://github.com/woocommerce/woocommerce/pull/35985)
* Performance - Split CALC_FOUND_ROW query into seperate count query for better performance. [#35468](https://github.com/woocommerce/woocommerce/pull/35468)
* Enhancement - Add a bottom padding to the whole form [#35721](https://github.com/woocommerce/woocommerce/pull/35721)
* Enhancement - Add a confirmation modal when the user tries to navigate away with unsaved changes [#35625](https://github.com/woocommerce/woocommerce/pull/35625)
* Enhancement - Add a default placeholder title for newly added attributes and always show remove button for attributes [#35904](https://github.com/woocommerce/woocommerce/pull/35904)
* Enhancement - Add help tip for Product Image and Product Gallery meta boxes [#35834](https://github.com/woocommerce/woocommerce/pull/35834)
* Enhancement - Add support for product attribute taxonomy template [#35617](https://github.com/woocommerce/woocommerce/pull/35617)
* Enhancement - Add warning banner that informs the user if they have conflicting tax display settings [#36010](https://github.com/woocommerce/woocommerce/pull/36010)
* Enhancement - Change the width of pricing fields, SKU and Shipping Class to 50% in big screens (>960px) in new product management experience [#35545](https://github.com/woocommerce/woocommerce/pull/35545)
* Enhancement - Fix price field currency symbol position [#35718](https://github.com/woocommerce/woocommerce/pull/35718)
* Enhancement - Improve element stacking in modals on tablet and mobile [#35733](https://github.com/woocommerce/woocommerce/pull/35733)
* Security - Customers REST API endpoint will now return user metadata only when requester has an administrator role [#36408](https://github.com/woocommerce/woocommerce/pull/36408).
= 7.2.3 2023-1-9
**WooCommerce Blocks 8.9.4**

View File

@ -51,7 +51,7 @@
"sass": "^1.49.9",
"sass-loader": "^10.2.1",
"syncpack": "^8.3.9",
"turbo": "^1.4.5",
"turbo": "^1.7.0",
"typescript": "^4.8.3",
"url-loader": "^1.1.2",
"webpack": "^5.70.0"

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Update eslint to 8.32.0 across the monorepo.

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Unify semver range for `config@3.3.7` (from `^3.3.7`). Fix `node_env_var_name is not defined` error.

View File

@ -29,7 +29,7 @@
"@jest/globals": "^27.5.1",
"@types/jest": "^27.4.1",
"@woocommerce/e2e-utils": "workspace:*",
"config": "^3.3.7"
"config": "3.3.7"
},
"peerDependencies": {
"@woocommerce/e2e-environment": "^0.2.3 || ^0.3.0",
@ -44,7 +44,7 @@
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@woocommerce/api": "^0.2.0",
"@woocommerce/eslint-plugin": "workspace:*",
"eslint": "^8.12.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest-mock-extended": "^1.0.18",

View File

@ -36,7 +36,7 @@
},
"devDependencies": {
"@woocommerce/eslint-plugin": "workspace:*",
"eslint": "^8.12.0"
"eslint": "^8.32.0"
},
"publishConfig": {
"access": "public"

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Update eslint to 8.32.0 across the monorepo.

View File

@ -55,7 +55,7 @@
"@typescript-eslint/parser": "^5.43.0",
"@woocommerce/eslint-plugin": "workspace:*",
"axios-mock-adapter": "^1.20.0",
"eslint": "^8.2.0",
"eslint": "^8.32.0",
"jest": "^27",
"ts-jest": "^27",
"typescript": "^4.8.3"

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Create tree-control component

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add TreeControl expand/collapse functionality.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add 6 basic fields to the product fields registry for use in extensibility within the new Product MVP.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Added LearnMore option as well as made it possible to use this button multiple instances on the page

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Move experimental product section components to @woocommerce/product-editor package.

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Altering styles to correctly target fields within slot fills on product editor.

View File

@ -0,0 +1,4 @@
Significance: patch
Type: add
Add an optional "InputProps" to experimental SelectControl component

View File

@ -1,4 +0,0 @@
Significance: minor
Type: tweak
Update spelling of Cancelled to Canceled for US English.

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Update eslint to 8.32.0 across the monorepo.

View File

@ -1,4 +1,4 @@
Significance: patch
Type: dev
Update readme
Migrate Table component to TS

View File

@ -0,0 +1,4 @@
Significance: minor
Type: fix
Refactor createOrderedChildren

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fix issue were Options tab was not showing up anymore in new product management screen.

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Include CSS for experimental tree control so it renders properly in Storybook.

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fix SelectControl and TreeControl styles.

View File

@ -0,0 +1,5 @@
Significance: patch
Type: fix
Comment: Move registerFill call to inside an useEffect since it was updating a component while rendering another component

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add new WooProductTabItem component for slot filling tab items.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Adding experimental component SlotContext

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Updating the product editor fill components to support multiple targets.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Adding ProductSectionLayout component and changing default order for WooProductSectionItem component.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Updating WooProductFieldItem to uniquely generate IDs with different sections.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Add deprecated message to packages moved to product-editor package.

View File

@ -44,6 +44,7 @@
"@woocommerce/navigation": "workspace:*",
"@wordpress/a11y": "3.5.0",
"@wordpress/api-fetch": "^6.0.1",
"@wordpress/base-styles": "^4.3.0",
"@wordpress/block-editor": "^9.8.0",
"@wordpress/block-library": "^7.16.0",
"@wordpress/blocks": "^11.18.0",
@ -129,7 +130,7 @@
"@wordpress/scripts": "^12.6.1",
"concurrently": "^7.0.0",
"css-loader": "^3.6.0",
"eslint": "^8.12.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"postcss-loader": "^3.0.0",
@ -164,5 +165,11 @@
"pnpm lint:fix",
"pnpm test-staged"
]
},
"pnpm": {
"overrides": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
}

View File

@ -7,6 +7,7 @@ import {
UseComboboxState,
UseComboboxStateChangeOptions,
useMultipleSelection,
GetInputPropsOptions,
} from 'downshift';
import {
useState,
@ -65,6 +66,7 @@ export type SelectControlProps< ItemType > = {
selected: ItemType | ItemType[] | null;
className?: string;
disabled?: boolean;
inputProps?: GetInputPropsOptions;
suffix?: JSX.Element | null;
/**
* This is a feature already implemented in downshift@7.0.0 through the
@ -119,6 +121,7 @@ function SelectControl< ItemType = DefaultItemType >( {
selected,
className,
disabled,
inputProps = {},
suffix = <SuffixIcon icon={ search } />,
__experimentalOpenMenuOnFocus = false,
}: SelectControlProps< ItemType > ) {
@ -268,6 +271,7 @@ function SelectControl< ItemType = DefaultItemType >( {
onBlur: () => setIsFocused( false ),
placeholder,
disabled,
...inputProps,
} ) }
suffix={ suffix }
>

View File

@ -0,0 +1,31 @@
/**
* External dependencies
*/
import { useEffect, useState } from 'react';
/**
* Internal dependencies
*/
import { TreeItemProps } from '../types';
export function useExpander( {
shouldItemBeExpanded,
item,
}: Pick< TreeItemProps, 'shouldItemBeExpanded' | 'item' > ) {
const [ isExpanded, setExpanded ] = useState( false );
useEffect( () => {
if (
item.children?.length &&
typeof shouldItemBeExpanded === 'function'
) {
setExpanded( shouldItemBeExpanded( item ) );
}
}, [ item, shouldItemBeExpanded ] );
function onToggleExpand() {
setExpanded( ( prev ) => ! prev );
}
return { isExpanded, onToggleExpand };
}

View File

@ -0,0 +1,50 @@
/**
* External dependencies
*/
import { useMemo } from 'react';
/**
* Internal dependencies
*/
import { Item, LinkedTree } from '../types';
type MemoItems = {
[ value: Item[ 'value' ] ]: LinkedTree;
};
function findChildren(
items: Item[],
parent?: Item[ 'parent' ],
memo: MemoItems = {}
): LinkedTree[] {
const children: Item[] = [];
const others: Item[] = [];
items.forEach( ( item ) => {
if ( item.parent === parent ) {
children.push( item );
} else {
others.push( item );
}
memo[ item.value ] = {
parent: undefined,
data: item,
children: [],
};
} );
return children.map( ( child ) => {
const linkedTree = memo[ child.value ];
linkedTree.parent = child.parent ? memo[ child.parent ] : undefined;
linkedTree.children = findChildren( others, child.value, memo );
return linkedTree;
} );
}
export function useLinkedTree( items: Item[] ): LinkedTree[] {
const linkedTree = useMemo( () => {
return findChildren( items, undefined, {} );
}, [ items ] );
return linkedTree;
}

View File

@ -0,0 +1,43 @@
/**
* External dependencies
*/
import React from 'react';
/**
* Internal dependencies
*/
import { TreeItemProps } from '../types';
import { useExpander } from './use-expander';
export function useTreeItem( {
item,
level,
shouldItemBeExpanded,
...props
}: TreeItemProps ) {
const nextLevel = level + 1;
const expander = useExpander( {
item,
shouldItemBeExpanded,
} );
return {
item,
level: nextLevel,
expander,
treeItemProps: {
...props,
},
headingProps: {
style: {
'--level': level,
} as React.CSSProperties,
},
treeProps: {
items: item.children,
level: nextLevel,
shouldItemBeExpanded,
},
};
}

View File

@ -0,0 +1,28 @@
/**
* External dependencies
*/
/**
* Internal dependencies
*/
import { TreeProps } from '../types';
export function useTree( {
ref,
items,
level = 1,
shouldItemBeExpanded,
...props
}: TreeProps ) {
return {
level,
items,
treeProps: {
...props,
},
treeItemProps: {
level,
shouldItemBeExpanded,
},
};
}

View File

@ -0,0 +1,4 @@
export * from './tree';
export * from './tree-control';
export * from './tree-item';
export * from './types';

View File

@ -0,0 +1,71 @@
/**
* External dependencies
*/
import { BaseControl, TextControl } from '@wordpress/components';
import React, { createElement, useCallback, useState } from 'react';
/**
* Internal dependencies
*/
import { TreeControl } from '../tree-control';
import { Item, LinkedTree } from '../types';
const listItems: Item[] = [
{ value: '1', label: 'Technology' },
{ value: '1.1', label: 'Notebooks', parent: '1' },
{ value: '1.2', label: 'Phones', parent: '1' },
{ value: '1.2.1', label: 'iPhone', parent: '1.2' },
{ value: '1.2.1.1', label: 'iPhone 14 Pro', parent: '1.2.1' },
{ value: '1.2.1.2', label: 'iPhone 14 Pro Max', parent: '1.2.1' },
{ value: '1.2.2', label: 'Samsung', parent: '1.2' },
{ value: '1.2.2.1', label: 'Samsung Galaxy 22 Plus', parent: '1.2.2' },
{ value: '1.2.2.2', label: 'Samsung Galaxy 22 Ultra', parent: '1.2.2' },
{ value: '1.3', label: 'Wearables', parent: '1' },
{ value: '2', label: 'Hardware' },
{ value: '2.1', label: 'CPU', parent: '2' },
{ value: '2.2', label: 'GPU', parent: '2' },
{ value: '2.3', label: 'Memory RAM', parent: '2' },
{ value: '3', label: 'Other' },
];
export const SimpleTree: React.FC = () => {
return (
<BaseControl label="Simple tree" id="simple-tree">
<TreeControl id="simple-tree" items={ listItems } />
</BaseControl>
);
};
function shouldItemBeExpanded( item: LinkedTree, filter: string ) {
if ( ! filter || ! item.children?.length ) return false;
return item.children.some( ( child ) => {
if ( new RegExp( filter, 'ig' ).test( child.data.label ) ) {
return true;
}
return shouldItemBeExpanded( child, filter );
} );
}
export const ExpandOnFilter: React.FC = () => {
const [ filter, setFilter ] = useState( '' );
return (
<>
<TextControl value={ filter } onChange={ setFilter } />
<BaseControl label="Expand on filter" id="expand-on-filter">
<TreeControl
id="expand-on-filter"
items={ listItems }
shouldItemBeExpanded={ ( item ) =>
shouldItemBeExpanded( item, filter )
}
/>
</BaseControl>
</>
);
};
export default {
title: 'WooCommerce Admin/experimental/TreeControl',
component: TreeControl,
};

View File

@ -0,0 +1,20 @@
/**
* External dependencies
*/
import { createElement, forwardRef } from 'react';
/**
* Internal dependencies
*/
import { useLinkedTree } from './hooks/use-linked-tree';
import { Tree } from './tree';
import { TreeControlProps } from './types';
export const TreeControl = forwardRef( function ForwardedTree(
{ items, ...props }: TreeControlProps,
ref: React.ForwardedRef< HTMLOListElement >
) {
const linkedTree = useLinkedTree( items );
return <Tree { ...props } ref={ ref } items={ linkedTree } />;
} );

View File

@ -0,0 +1,42 @@
.experimental-woocommerce-tree-item {
margin: 0;
&__heading {
display: flex;
flex-grow: 1;
gap: $gap-smaller;
min-height: $gap-largest;
padding: 0 $gap-small 0 calc( ( var( --level ) - 1 ) * ( $gap + $gap-small ) + $gap-small );
border-radius: 2px;
&:hover,
&:focus-within {
outline: 1.5px solid var( --wp-admin-theme-color );
outline-offset: -1.5px;
}
&:hover,
&:focus-within {
background-color: $gray-100;
}
}
&__label {
display: flex;
flex-grow: 1;
align-items: center;
padding: $gap-smaller $gap-small $gap-smaller 0;
position: relative;
> span {
display: block;
}
}
&__expander {
display: flex;
align-items: center;
.components-button {
padding: 0;
}
}
}

View File

@ -0,0 +1,69 @@
/**
* External dependencies
*/
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { chevronDown, chevronUp } from '@wordpress/icons';
import classNames from 'classnames';
import { createElement, forwardRef } from 'react';
/**
* Internal dependencies
*/
import { useTreeItem } from './hooks/use-tree-item';
import { Tree } from './tree';
import { TreeItemProps } from './types';
export const TreeItem = forwardRef( function ForwardedTreeItem(
props: TreeItemProps,
ref: React.ForwardedRef< HTMLLIElement >
) {
const {
item,
treeItemProps,
headingProps,
treeProps,
expander: { isExpanded, onToggleExpand },
} = useTreeItem( {
...props,
ref,
} );
return (
<li
{ ...treeItemProps }
className={ classNames(
treeItemProps.className,
'experimental-woocommerce-tree-item'
) }
>
<div
{ ...headingProps }
className="experimental-woocommerce-tree-item__heading"
>
<div className="experimental-woocommerce-tree-item__label">
<span>{ item.data.label }</span>
</div>
{ Boolean( item.children?.length ) && (
<div className="experimental-woocommerce-tree-item__expander">
<Button
icon={ isExpanded ? chevronUp : chevronDown }
onClick={ onToggleExpand }
className="experimental-woocommerce-tree-item__expander"
aria-label={
isExpanded
? __( 'Collapse', 'woocommerce' )
: __( 'Expand', 'woocommerce' )
}
/>
</div>
) }
</div>
{ Boolean( item.children.length ) && isExpanded && (
<Tree { ...treeProps } />
) }
</li>
);
} );

View File

@ -0,0 +1,15 @@
@import './tree-item.scss';
.experimental-woocommerce-tree {
list-style: none;
padding: 0;
margin: 0;
&--level-1 {
max-height: 280px;
overflow-y: auto;
background-color: $white;
border: 1px solid $gray-400;
border-radius: 2px;
}
}

View File

@ -0,0 +1,42 @@
/**
* External dependencies
*/
import classNames from 'classnames';
import { createElement, forwardRef } from 'react';
/**
* Internal dependencies
*/
import { useTree } from './hooks/use-tree';
import { TreeItem } from './tree-item';
import { TreeProps } from './types';
export const Tree = forwardRef( function ForwardedTree(
props: TreeProps,
ref: React.ForwardedRef< HTMLOListElement >
) {
const { level, items, treeProps, treeItemProps } = useTree( {
...props,
ref,
} );
if ( ! items.length ) return null;
return (
<ol
{ ...treeProps }
className={ classNames(
treeProps.className,
'experimental-woocommerce-tree',
`experimental-woocommerce-tree--level-${ level }`
) }
>
{ items.map( ( child ) => (
<TreeItem
{ ...treeItemProps }
key={ child.data.value }
item={ child }
/>
) ) }
</ol>
);
} );

View File

@ -0,0 +1,47 @@
export interface Item {
parent?: string;
value: string;
label: string;
}
export interface LinkedTree {
parent?: LinkedTree;
data: Item;
children: LinkedTree[];
}
export type TreeProps = React.DetailedHTMLProps<
React.OlHTMLAttributes< HTMLOListElement >,
HTMLOListElement
> & {
level?: number;
items: LinkedTree[];
/**
* Return if the tree item passed in should be expanded.
*
* @example
* <Tree
* shouldItemBeExpanded={
* ( item ) => checkExpanded( item, filter )
* }
* />
*
* @param item The tree item to determine if should be expanded.
*
* @see {@link LinkedTree}
*/
shouldItemBeExpanded?( item: LinkedTree ): boolean;
};
export type TreeItemProps = React.DetailedHTMLProps<
React.LiHTMLAttributes< HTMLLIElement >,
HTMLLIElement
> & {
level: number;
item: LinkedTree;
shouldItemBeExpanded?( item: LinkedTree ): boolean;
};
export type TreeControlProps = Omit< TreeProps, 'items' | 'level' > & {
items: Item[];
};

View File

@ -87,3 +87,17 @@ export { CollapsibleContent } from './collapsible-content';
export { createOrderedChildren, sortFillsByOrder } from './utils';
export { WooProductFieldItem as __experimentalWooProductFieldItem } from './woo-product-field-item';
export { WooProductSectionItem as __experimentalWooProductSectionItem } from './woo-product-section-item';
export { WooProductTabItem as __experimentalWooProductTabItem } from './woo-product-tab-item';
export * from './product-fields';
export {
SlotContextProvider,
useSlotContext,
SlotContextType,
SlotContextHelpersType,
} from './slot-context';
// Exports below can be removed once the @woocommerce/product-editor package is released.
export {
ProductSectionLayout as __experimentalProductSectionLayout,
ProductFieldSection as __experimentalProductFieldSection,
} from './product-section-layout';

View File

@ -9,7 +9,7 @@ import {
useState,
useEffect,
} from '@wordpress/element';
import { SyntheticEvent } from 'react';
import { SyntheticEvent, useCallback } from 'react';
import { useDispatch, useSelect } from '@wordpress/data';
import { PLUGINS_STORE_NAME, InstallPluginsResponse } from '@woocommerce/data';
@ -25,6 +25,11 @@ type PluginsProps = {
pluginSlugs?: string[];
onAbort?: () => void;
abortText?: string;
installText?: string;
installButtonVariant?: Button.BaseProps[ 'variant' ];
learnMoreLink?: string;
learnMoreText?: string;
onLearnMore?: () => void;
};
export const Plugins = ( {
@ -34,10 +39,17 @@ export const Plugins = ( {
onError = () => null,
pluginSlugs = [ 'jetpack', 'woocommerce-services' ],
onSkip,
installText = __( 'Install & enable', 'woocommerce' ),
skipText = __( 'No thanks', 'woocommerce' ),
abortText = __( 'Abort', 'woocommerce' ),
installButtonVariant = 'primary',
learnMoreLink,
learnMoreText = __( 'Learn more', 'woocommerce' ),
onLearnMore,
}: PluginsProps ) => {
const [ hasErrors, setHasErrors ] = useState( false );
// Tracks action so that multiple instances of this button don't all light up when one is clicked
const [ hasBeenClicked, setHasBeenClicked ] = useState( false );
const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME );
const { isRequesting } = useSelect( ( select ) => {
const { getActivePlugins, getInstalledPlugins, isPluginsRequesting } =
@ -52,48 +64,56 @@ export const Plugins = ( {
};
} );
const handleErrors = (
errors: unknown,
response: InstallPluginsResponse
) => {
setHasErrors( true );
const handleErrors = useCallback(
( errors: unknown, response: InstallPluginsResponse ) => {
setHasErrors( true );
onError( errors, response );
};
onError( errors, response );
},
[ onError ]
);
const handleSuccess = (
plugins: string[],
response: InstallPluginsResponse
) => {
onComplete( plugins, response );
};
const handleSuccess = useCallback(
( plugins: string[], response: InstallPluginsResponse ) => {
onComplete( plugins, response );
},
[ onComplete ]
);
const installAndActivate = async (
event?: SyntheticEvent< HTMLAnchorElement >
) => {
if ( event ) {
event.preventDefault();
}
const installAndActivate = useCallback(
async ( event?: SyntheticEvent< HTMLAnchorElement > ) => {
if ( event ) {
event.preventDefault();
}
// Avoid double activating.
if ( isRequesting ) {
return false;
}
// Avoid double activating.
if ( isRequesting ) {
return false;
}
installAndActivatePlugins( pluginSlugs )
.then( ( response ) => {
handleSuccess( response.data.activated, response );
} )
.catch( ( response ) => {
handleErrors( response.errors, response );
} );
};
installAndActivatePlugins( pluginSlugs )
.then( ( response ) => {
handleSuccess( response.data.activated, response );
} )
.catch( ( response ) => {
setHasBeenClicked( false );
handleErrors( response.errors, response );
} );
},
[
handleErrors,
handleSuccess,
installAndActivatePlugins,
isRequesting,
pluginSlugs,
]
);
useEffect( () => {
if ( autoInstall ) {
installAndActivate();
}
}, [] );
}, [ autoInstall, installAndActivate ] );
if ( hasErrors ) {
return (
@ -131,17 +151,32 @@ export const Plugins = ( {
return (
<>
<Button
isBusy={ isRequesting }
isPrimary
onClick={ installAndActivate }
isBusy={ isRequesting && hasBeenClicked }
variant={
isRequesting && hasBeenClicked
? 'primary' // set to primary when busy, the other variants look weird when combined with isBusy
: installButtonVariant
}
disabled={ isRequesting && hasBeenClicked }
onClick={ () => {
setHasBeenClicked( true );
installAndActivate();
} }
>
{ __( 'Install & enable', 'woocommerce' ) }
{ installText }
</Button>
{ onSkip && (
<Button isTertiary onClick={ onSkip }>
{ skipText }
</Button>
) }
{ learnMoreLink && (
<a href={ learnMoreLink } target="_blank" rel="noreferrer">
<Button isTertiary onClick={ onLearnMore }>
{ learnMoreText }
</Button>
</a>
) }
{ onAbort && (
<Button isTertiary onClick={ onAbort }>
{ abortText }

View File

@ -3,6 +3,10 @@
*/
import { select } from '@wordpress/data';
import { createElement } from '@wordpress/element';
import {
// @ts-expect-error `__experimentalInputControl` does exist.
__experimentalInputControl as InputControl,
} from '@wordpress/components';
/**
* Internal dependencies
@ -19,10 +23,7 @@ export function renderField( name: string, props: Record< string, any > ) {
return <fieldConfig.render { ...props } />;
}
if ( fieldConfig.type ) {
return createElement( 'input', {
type: fieldConfig.type,
...props,
} );
return <InputControl type={ fieldConfig.type } { ...props } />;
}
return null;
}

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { ComponentType } from 'react';
/**
* Internal dependencies
*/
import { ProductFieldDefinition } from '../../store/types';
import render from './render';
export const basicSelectControlSettings: ProductFieldDefinition = {
name: 'basic-select-control',
render: render as ComponentType,
};

View File

@ -0,0 +1,34 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { RadioControl, SelectControl } from '@wordpress/components';
/**
* Internal dependencies
*/
import { BaseProductFieldProps } from '../types';
type SelectControlFieldProps = BaseProductFieldProps< string | string[] > & {
multiple?: boolean;
options: SelectControl.Option[];
};
const SelectControlField: React.FC< SelectControlFieldProps > = ( {
label,
value,
onChange,
multiple,
options = [],
} ) => {
return (
<SelectControl
multiple={ multiple }
label={ label }
options={ options }
onChange={ onChange }
value={ value }
/>
);
};
export default SelectControlField;

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { ComponentType } from 'react';
/**
* Internal dependencies
*/
import { ProductFieldDefinition } from '../../store/types';
import render from './render';
export const checkboxSettings: ProductFieldDefinition = {
name: 'checkbox',
render: render as ComponentType,
};

View File

@ -0,0 +1,28 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { CheckboxControl } from '@wordpress/components';
/**
* Internal dependencies
*/
import { BaseProductFieldProps } from '../types';
type CheckboxFieldProps = BaseProductFieldProps< boolean >;
const CheckboxField: React.FC< CheckboxFieldProps > = ( {
label,
value,
onChange,
} ) => {
return (
<CheckboxControl
label={ label }
onChange={ onChange }
selected={ value }
/>
);
};
export default CheckboxField;

View File

@ -0,0 +1,29 @@
/**
* Internal dependencies
*/
import { registerProductField } from '../api';
import { ProductFieldDefinition } from '../store/types';
import { basicSelectControlSettings } from './basic-select-control';
import { checkboxSettings } from './checkbox';
import { radioSettings } from './radio';
import { textSettings } from './text';
import { toggleSettings } from './toggle';
const getAllProductFields = (): ProductFieldDefinition[] =>
[
...[ 'number' ].map( ( type ) => ( {
name: type,
type,
} ) ),
textSettings,
toggleSettings,
radioSettings,
basicSelectControlSettings,
checkboxSettings,
].filter( Boolean );
export const registerCoreProductFields = ( fields = getAllProductFields() ) => {
fields.forEach( ( field ) => {
registerProductField( field.name, field );
} );
};

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { ComponentType } from 'react';
/**
* Internal dependencies
*/
import { ProductFieldDefinition } from '../../store/types';
import render from './render';
export const radioSettings: ProductFieldDefinition = {
name: 'radio',
render: render as ComponentType,
};

View File

@ -0,0 +1,34 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { RadioControl } from '@wordpress/components';
/**
* Internal dependencies
*/
import { BaseProductFieldProps } from '../types';
type RadioFieldProps = BaseProductFieldProps< string > & {
options: {
label: string;
value: string;
}[];
};
const RadioField: React.FC< RadioFieldProps > = ( {
label,
value,
onChange,
options = [],
} ) => {
return (
<RadioControl
label={ label }
options={ options }
onChange={ onChange }
selected={ value }
/>
);
};
export default RadioField;

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { ComponentType } from 'react';
/**
* Internal dependencies
*/
import { ProductFieldDefinition } from '../../store/types';
import render from './render';
export const textSettings: ProductFieldDefinition = {
name: 'text',
render: render as ComponentType,
};

View File

@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { TextControl } from '@wordpress/components';
/**
* Internal dependencies
*/
import { BaseProductFieldProps } from '../types';
type TextFieldProps = BaseProductFieldProps< string >;
const TextField: React.FC< TextFieldProps > = ( {
label,
value,
onChange,
} ) => {
return (
<TextControl label={ label } onChange={ onChange } value={ value } />
);
};
export default TextField;

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { ComponentType } from 'react';
/**
* Internal dependencies
*/
import { ProductFieldDefinition } from '../../store/types';
import render from './render';
export const toggleSettings: ProductFieldDefinition = {
name: 'toggle',
render: render as ComponentType,
};

View File

@ -0,0 +1,40 @@
/**
* External dependencies
*/
import { createElement, Fragment } from '@wordpress/element';
import { ToggleControl } from '@wordpress/components';
/**
* Internal dependencies
*/
import { BaseProductFieldProps } from '../types';
import { Tooltip } from '../../../tooltip';
type ToggleFieldProps = BaseProductFieldProps< boolean > & {
tooltip?: string;
};
const ToggleField: React.FC< ToggleFieldProps > = ( {
label,
value,
onChange,
tooltip,
disabled = false,
} ) => {
return (
<ToggleControl
label={
<>
{ label }
{ tooltip && <Tooltip text={ tooltip } /> }
</>
}
checked={ value }
onChange={ onChange }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore disabled prop exists
disabled={ disabled }
/>
);
};
export default ToggleField;

View File

@ -0,0 +1,6 @@
export type BaseProductFieldProps< T > = {
value: T;
onChange: ( value: T ) => void;
label: string;
disabled?: boolean;
};

View File

@ -1,2 +1,3 @@
export { store } from './store';
export * from './api';
export * from './fields';

View File

@ -1,11 +1,11 @@
/**
* External dependencies
*/
import { ComponentType } from 'react';
import { ComponentType, HTMLInputTypeAttribute } from 'react';
export type ProductFieldDefinition = {
name: string;
type?: string;
type?: HTMLInputTypeAttribute;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render?: ComponentType;
};

View File

@ -3,56 +3,92 @@
*/
import React from 'react';
import { useState, createElement } from '@wordpress/element';
import { createRegistry, RegistryProvider, select } from '@wordpress/data';
import {
// @ts-expect-error `__experimentalInputControl` does exist.
__experimentalInputControl as InputControl,
} from '@wordpress/components';
import { createRegistry, RegistryProvider } from '@wordpress/data';
/**
* Internal dependencies
*/
import { store } from '../store';
import { registerProductField, renderField } from '../api';
import { renderField } from '../api';
import { registerCoreProductFields } from '../fields';
const registry = createRegistry();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
registry.register( store );
registerProductField( 'text', {
name: 'text',
render: ( props ) => {
return <InputControl type="text" { ...props } />;
},
} );
registerCoreProductFields();
registerProductField( 'number', {
name: 'number',
render: () => {
return <InputControl type="number" />;
const fieldConfigs = [
{
name: 'text-field',
type: 'text',
label: 'Text field',
},
} );
{
name: 'number-field',
type: 'number',
label: 'Number field',
},
{
name: 'toggle-field',
type: 'toggle',
label: 'Toggle field',
},
{
name: 'checkbox-field',
type: 'checkbox',
label: 'Checkbox field',
},
{
name: 'radio-field',
type: 'radio',
label: 'Radio field',
options: [
{ label: 'Option', value: 'option' },
{ label: 'Option 2', value: 'option2' },
{ label: 'Option 3', value: 'option3' },
],
},
{
name: 'basic-select-control-field',
type: 'basic-select-control',
label: 'Basic select control field',
options: [
{ label: 'Option', value: 'option' },
{ label: 'Option 2', value: 'option2' },
{ label: 'Option 3', value: 'option3' },
],
},
];
const RenderField = () => {
const fields: string[] = select( store ).getRegisteredProductFields();
const [ selectedField, setSelectedField ] = useState(
fields ? fields[ 0 ] : undefined
fieldConfigs[ 0 ].name || undefined
);
const [ value, setValue ] = useState();
const handleChange = ( event ) => {
setSelectedField( event.target.value );
};
const selectedFieldConfig = fieldConfigs.find(
( f ) => f.name === selectedField
);
return (
<div>
<select value={ selectedField } onChange={ handleChange }>
{ fields.map( ( field ) => (
<option key={ field } value={ field }>
{ field }
{ fieldConfigs.map( ( field ) => (
<option key={ field.name } value={ field.name }>
{ field.label }
</option>
) ) }
</select>
{ selectedField && renderField( selectedField, { name: 'test' } ) }
{ selectedFieldConfig &&
renderField( selectedFieldConfig.type, {
value,
onChange: setValue,
...selectedFieldConfig,
} ) }
</div>
);
};
@ -65,6 +101,21 @@ export const Basic: React.FC = () => {
);
};
export const ToggleWithTooltip: React.FC = () => {
const [ value, setValue ] = useState();
return (
<RegistryProvider value={ registry }>
{ renderField( 'toggle', {
value,
onChange: setValue,
name: 'toggle',
label: 'Toggle with Tooltip',
tooltip: 'This is a sample tooltip',
} ) }
</RegistryProvider>
);
};
export default {
title: 'WooCommerce Admin/experimental/product-fields',
component: Basic,

View File

@ -0,0 +1,2 @@
export * from './product-section-layout';
export * from './product-field-section';

View File

@ -0,0 +1,47 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { Card, CardBody } from '@wordpress/components';
import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
*/
import { ProductSectionLayout } from './product-section-layout';
import { WooProductFieldItem } from '../woo-product-field-item';
type ProductFieldSectionProps = {
id: string;
title: string;
description: string | JSX.Element;
className?: string;
};
export const ProductFieldSection: React.FC< ProductFieldSectionProps > = ( {
id,
title,
description,
className,
children,
} ) => {
deprecated( `__experimentalProductFieldSection`, {
version: '13.0.0',
plugin: '@woocommerce/components',
hint: 'Moved to @woocommerce/product-editor package: import { __experimentalProductFieldSection } from @woocommerce/product-editor',
} );
return (
<ProductSectionLayout
title={ title }
description={ description }
className={ className }
>
<Card>
<CardBody>
{ children }
<WooProductFieldItem.Slot section={ id } />
</CardBody>
</Card>
</ProductSectionLayout>
);
};

View File

@ -0,0 +1,44 @@
/**
* External dependencies
*/
import { Children, isValidElement, createElement } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
*/
import { FormSection } from '../form-section';
type ProductSectionLayoutProps = {
title: string;
description: string | JSX.Element;
className?: string;
};
export const ProductSectionLayout: React.FC< ProductSectionLayoutProps > = ( {
title,
description,
className,
children,
} ) => {
deprecated( `__experimentalProductSectionLayout`, {
version: '13.0.0',
plugin: '@woocommerce/components',
hint: 'Moved to @woocommerce/product-editor package: import { __experimentalProductSectionLayout } from @woocommerce/product-editor',
} );
return (
<FormSection
title={ title }
description={ description }
className={ className }
>
{ Children.map( children, ( child ) => {
if ( isValidElement( child ) && child.props.onChange ) {
return (
<div className="product-field-layout">{ child }</div>
);
}
return child;
} ) }
</FormSection>
);
};

View File

@ -0,0 +1,52 @@
.woocommerce-form-section {
a {
text-decoration: none;
}
&__content {
.components-card {
border: 1px solid $gray-400;
border-radius: 2px;
box-shadow: none;
&__body {
padding: $gap-large;
.components-base-control,
.components-dropdown,
.woocommerce-rich-text-editor {
&:not(:first-child):not(.components-radio-control) {
margin-top: $gap-large - $gap-smaller;
margin-bottom: 0;
}
}
}
}
.woocommerce-product-form__field:not(:first-child) {
margin-top: $gap-large;
> .components-base-control {
margin-bottom: 0;
}
}
.components-radio-control .components-v-stack {
gap: $gap-small;
}
.woocommerce-collapsible-content {
margin-top: $gap-large;
}
}
&__header {
p > span {
display: block;
margin-bottom: $gap-smaller;
}
}
&:not(:first-child) {
margin-top: $gap-largest;
}
}

View File

@ -0,0 +1 @@
export * from './slot-context';

View File

@ -0,0 +1,104 @@
/**
* External dependencies
*/
import {
createElement,
createContext,
useContext,
useCallback,
useReducer,
} from '@wordpress/element';
type FillConfigType = {
visible: boolean;
};
type FillType = Record< string, FillConfigType >;
type FillCollection = readonly ( readonly JSX.Element[] )[];
export type SlotContextHelpersType = {
hideFill: ( id: string ) => void;
showFill: ( id: string ) => void;
getFills: () => FillType;
};
export type SlotContextType = {
fills: FillType;
getFillHelpers: () => SlotContextHelpersType;
registerFill: ( id: string ) => void;
filterRegisteredFills: ( fillsArrays: FillCollection ) => FillCollection;
};
const SlotContext = createContext< SlotContextType | undefined >( undefined );
export const SlotContextProvider: React.FC = ( { children } ) => {
const [ fills, updateFills ] = useReducer(
( data: FillType, updates: FillType ) => ( { ...data, ...updates } ),
{}
);
const updateFillConfig = (
id: string,
update: Partial< FillConfigType >
) => {
if ( ! fills[ id ] ) {
throw new Error( `No fill found with ID: ${ id }` );
}
updateFills( { [ id ]: { ...fills[ id ], ...update } } );
};
const registerFill = useCallback(
( id: string ) => {
if ( fills[ id ] ) {
return;
}
updateFills( { [ id ]: { visible: true } } );
},
[ fills ]
);
const hideFill = useCallback(
( id: string ) => updateFillConfig( id, { visible: false } ),
[ fills ]
);
const showFill = useCallback(
( id: string ) => updateFillConfig( id, { visible: true } ),
[ fills ]
);
const getFills = useCallback( () => ( { ...fills } ), [ fills ] );
return (
<SlotContext.Provider
value={ {
registerFill,
getFillHelpers() {
return { hideFill, showFill, getFills };
},
filterRegisteredFills( fillsArrays: FillCollection ) {
return fillsArrays.filter(
( arr ) =>
fills[ arr[ 0 ].props._id ]?.visible !== false
);
},
fills,
} }
>
{ children }
</SlotContext.Provider>
);
};
export const useSlotContext = () => {
const slotContext = useContext( SlotContext );
if ( slotContext === undefined ) {
throw new Error(
'useSlotContext must be used within a SlotContextProvider'
);
}
return slotContext;
};

View File

@ -55,3 +55,5 @@
@import 'tour-kit/style.scss';
@import 'collapsible-content/style.scss';
@import 'form/style.scss';
@import 'experimental-tree-control/tree.scss';
@import 'product-section-layout/style.scss';

View File

@ -1,39 +1,32 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import { createElement } from '@wordpress/element';
import React from 'react';
type EmptyTableProps = {
children: React.ReactNode;
/** An integer with the number of rows the box should occupy. */
numberOfRows?: number;
};
/**
* `EmptyTable` displays a blank space with an optional message passed as a children node
* with the purpose of replacing a table with no rows.
* It mimics the same height a table would have according to the `numberOfRows` prop.
*
* @param {Object} props
* @param {Node} props.children
* @param {number} props.numberOfRows
* @return {Object} -
*/
const EmptyTable = ( { children, numberOfRows } ) => {
const EmptyTable = ( { children, numberOfRows = 5 }: EmptyTableProps ) => {
return (
<div
className="woocommerce-table is-empty"
style={ { '--number-of-rows': numberOfRows } }
style={
{ '--number-of-rows': numberOfRows } as React.CSSProperties
}
>
{ children }
</div>
);
};
EmptyTable.propTypes = {
/**
* An integer with the number of rows the box should occupy.
*/
numberOfRows: PropTypes.number,
};
EmptyTable.defaultProps = {
numberOfRows: 5,
};
export default EmptyTable;

View File

@ -1,384 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import classnames from 'classnames';
import {
Card,
CardBody,
CardFooter,
CardHeader,
__experimentalText as Text,
} from '@wordpress/components';
import { createElement, Component, Fragment } from '@wordpress/element';
import { find, first, isEqual, without } from 'lodash';
import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
import EllipsisMenu from '../ellipsis-menu';
import MenuItem from '../ellipsis-menu/menu-item';
import MenuTitle from '../ellipsis-menu/menu-title';
import Pagination from '../pagination';
import Table from './table';
import TablePlaceholder from './placeholder';
import TableSummary, { TableSummaryPlaceholder } from './summary';
/**
* This is an accessible, sortable, and scrollable table for displaying tabular data (like revenue and other analytics data).
* It accepts `headers` for column headers, and `rows` for the table content.
* `rowHeader` can be used to define the index of the row header (or false if no header).
*
* `TableCard` serves as Card wrapper & contains a card header, `<Table />`, `<TableSummary />`, and `<Pagination />`.
* This includes filtering and comparison functionality for report pages.
*/
class TableCard extends Component {
constructor( props ) {
super( props );
const showCols = this.getShowCols( props.headers );
this.state = { showCols };
this.onColumnToggle = this.onColumnToggle.bind( this );
this.onPageChange = this.onPageChange.bind( this );
}
componentDidUpdate( { headers: prevHeaders, query: prevQuery } ) {
const { headers, onColumnsChange, query } = this.props;
const { showCols } = this.state;
if ( ! isEqual( headers, prevHeaders ) ) {
/* eslint-disable react/no-did-update-set-state */
this.setState( {
showCols: this.getShowCols( headers ),
} );
/* eslint-enable react/no-did-update-set-state */
}
if (
query.orderby !== prevQuery.orderby &&
! showCols.includes( query.orderby )
) {
const newShowCols = showCols.concat( query.orderby );
/* eslint-disable react/no-did-update-set-state */
this.setState( {
showCols: newShowCols,
} );
/* eslint-enable react/no-did-update-set-state */
onColumnsChange( newShowCols );
}
}
getShowCols( headers ) {
return headers
.map( ( { key, visible } ) => {
if ( typeof visible === 'undefined' || visible ) {
return key;
}
return false;
} )
.filter( Boolean );
}
getVisibleHeaders() {
const { headers } = this.props;
const { showCols } = this.state;
return headers.filter( ( { key } ) => showCols.includes( key ) );
}
getVisibleRows() {
const { headers, rows } = this.props;
const { showCols } = this.state;
return rows.map( ( row ) => {
return headers
.map( ( { key }, i ) => {
return showCols.includes( key ) && row[ i ];
} )
.filter( Boolean );
} );
}
onColumnToggle( key ) {
const { headers, query, onQueryChange, onColumnsChange } = this.props;
return () => {
this.setState( ( prevState ) => {
const hasKey = prevState.showCols.includes( key );
if ( hasKey ) {
// Handle hiding a sorted column
if ( query.orderby === key ) {
const defaultSort =
find( headers, { defaultSort: true } ) ||
first( headers ) ||
{};
onQueryChange( 'sort' )( defaultSort.key, 'desc' );
}
const showCols = without( prevState.showCols, key );
onColumnsChange( showCols, key );
return { showCols };
}
const showCols = [ ...prevState.showCols, key ];
onColumnsChange( showCols, key );
return { showCols };
} );
};
}
onPageChange( ...params ) {
const { onPageChange, onQueryChange } = this.props;
if ( onPageChange ) {
onPageChange( ...params );
}
if ( onQueryChange ) {
onQueryChange( 'paged' )( ...params );
}
}
render() {
const {
actions,
className,
hasSearch,
isLoading,
onQueryChange,
onSort,
query,
rowHeader,
rowsPerPage,
showMenu,
summary,
title,
totalRows,
rowKey,
emptyMessage,
} = this.props;
const { showCols } = this.state;
const allHeaders = this.props.headers;
const headers = this.getVisibleHeaders();
const rows = this.getVisibleRows();
const classes = classnames( 'woocommerce-table', className, {
'has-actions': !! actions,
'has-menu': showMenu,
'has-search': hasSearch,
} );
return (
<Card className={ classes }>
<CardHeader>
<Text size={ 16 } weight={ 600 } as="h2" color="#23282d">
{ title }
</Text>
<div className="woocommerce-table__actions">
{ actions }
</div>
{ showMenu && (
<EllipsisMenu
label={ __(
'Choose which values to display',
'woocommerce'
) }
renderContent={ () => (
<Fragment>
<MenuTitle>
{ __( 'Columns:', 'woocommerce' ) }
</MenuTitle>
{ allHeaders.map(
( { key, label, required } ) => {
if ( required ) {
return null;
}
return (
<MenuItem
checked={ showCols.includes(
key
) }
isCheckbox
isClickable
key={ key }
onInvoke={ this.onColumnToggle(
key
) }
>
{ label }
</MenuItem>
);
}
) }
</Fragment>
) }
/>
) }
</CardHeader>
<CardBody size={ null }>
{ isLoading ? (
<Fragment>
<span className="screen-reader-text">
{ __(
'Your requested data is loading',
'woocommerce'
) }
</span>
<TablePlaceholder
numberOfRows={ rowsPerPage }
headers={ headers }
rowHeader={ rowHeader }
caption={ title }
query={ query }
/>
</Fragment>
) : (
<Table
rows={ rows }
headers={ headers }
rowHeader={ rowHeader }
caption={ title }
query={ query }
onSort={ onSort || onQueryChange( 'sort' ) }
rowKey={ rowKey }
emptyMessage={ emptyMessage }
/>
) }
</CardBody>
<CardFooter justify="center">
{ isLoading ? (
<TableSummaryPlaceholder />
) : (
<Fragment>
<Pagination
key={ parseInt( query.paged, 10 ) || 1 }
page={ parseInt( query.paged, 10 ) || 1 }
perPage={ rowsPerPage }
total={ totalRows }
onPageChange={ this.onPageChange }
onPerPageChange={ onQueryChange( 'per_page' ) }
/>
{ summary && <TableSummary data={ summary } /> }
</Fragment>
) }
</CardFooter>
</Card>
);
}
}
TableCard.propTypes = {
/**
* If a search is provided in actions and should reorder actions on mobile.
*/
hasSearch: PropTypes.bool,
/**
* An array of column headers (see `Table` props).
*/
headers: PropTypes.arrayOf(
PropTypes.shape( {
hiddenByDefault: PropTypes.bool,
defaultSort: PropTypes.bool,
isSortable: PropTypes.bool,
key: PropTypes.string,
label: PropTypes.oneOfType( [ PropTypes.string, PropTypes.node ] ),
required: PropTypes.bool,
} )
),
/**
* A list of IDs, matching to the row list so that ids[ 0 ] contains the object ID for the object displayed in row[ 0 ].
*/
ids: PropTypes.arrayOf( PropTypes.number ),
/**
* Defines if the table contents are loading.
* It will display `TablePlaceholder` component instead of `Table` if that's the case.
*/
isLoading: PropTypes.bool,
/**
* A function which returns a callback function to update the query string for a given `param`.
*/
onQueryChange: PropTypes.func,
/**
* A function which returns a callback function which is called upon the user changing the visiblity of columns.
*/
onColumnsChange: PropTypes.func,
/**
* A function which is called upon the user changing the sorting of the table.
*/
onSort: PropTypes.func,
/**
* An object of the query parameters passed to the page, ex `{ page: 2, per_page: 5 }`.
*/
query: PropTypes.object,
/**
* Which column should be the row header, defaults to the first item (`0`) (but could be set to `1`, if the first col
* is checkboxes, for example). Set to false to disable row headers.
*/
rowHeader: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
/**
* An array of arrays of display/value object pairs (see `Table` props).
*/
rows: PropTypes.arrayOf(
PropTypes.arrayOf(
PropTypes.shape( {
display: PropTypes.node,
value: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.number,
PropTypes.bool,
] ),
} )
)
).isRequired,
/**
* The total number of rows to display per page.
*/
rowsPerPage: PropTypes.number.isRequired,
/**
* Boolean to determine whether or not ellipsis menu is shown.
*/
showMenu: PropTypes.bool,
/**
* An array of objects with `label` & `value` properties, which display in a line under the table.
* Optional, can be left off to show no summary.
*/
summary: PropTypes.arrayOf(
PropTypes.shape( {
label: PropTypes.node,
value: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.number,
] ),
} )
),
/**
* The title used in the card header, also used as the caption for the content in this table.
*/
title: PropTypes.string.isRequired,
/**
* The total number of rows (across all pages).
*/
totalRows: PropTypes.number.isRequired,
/**
* The rowKey used for the key value on each row, this can be a string of the key or a function that returns the value.
* This uses the index if not defined.
*/
rowKey: PropTypes.func,
/**
* Customize the message to show when there are no rows in the table.
*/
emptyMessage: PropTypes.string,
};
TableCard.defaultProps = {
isLoading: false,
onQueryChange: () => () => {},
onColumnsChange: () => {},
onSort: undefined,
query: {},
rowHeader: 0,
rows: [],
showMenu: true,
emptyMessage: undefined,
};
export default TableCard;

View File

@ -0,0 +1,248 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import classnames from 'classnames';
import { createElement, Fragment, useState } from '@wordpress/element';
import { find, first, without } from 'lodash';
import React from 'react';
import {
Card,
CardBody,
CardFooter,
CardHeader,
// @ts-expect-error: Suppressing Module '"@wordpress/components"' has no exported member '__experimentalText'
__experimentalText as Text,
} from '@wordpress/components';
/**
* Internal dependencies
*/
import EllipsisMenu from '../ellipsis-menu';
import MenuItem from '../ellipsis-menu/menu-item';
import MenuTitle from '../ellipsis-menu/menu-title';
import Pagination from '../pagination';
import Table from './table';
import TablePlaceholder from './placeholder';
import TableSummary, { TableSummaryPlaceholder } from './summary';
import { TableCardProps } from './types';
const defaultOnQueryChange =
( param: string ) => ( path?: string, direction?: string ) => {};
const defaultOnColumnsChange = (
showCols: Array< string >,
key?: string
) => {};
/**
* This is an accessible, sortable, and scrollable table for displaying tabular data (like revenue and other analytics data).
* It accepts `headers` for column headers, and `rows` for the table content.
* `rowHeader` can be used to define the index of the row header (or false if no header).
*
* `TableCard` serves as Card wrapper & contains a card header, `<Table />`, `<TableSummary />`, and `<Pagination />`.
* This includes filtering and comparison functionality for report pages.
*/
const TableCard: React.VFC< TableCardProps > = ( {
actions,
className,
hasSearch,
headers = [],
ids,
isLoading = false,
onQueryChange = defaultOnQueryChange,
onColumnsChange = defaultOnColumnsChange,
onSort,
query = {},
rowHeader = 0,
rows = [],
rowsPerPage,
showMenu = true,
summary,
title,
totalRows,
rowKey,
emptyMessage = undefined,
...props
} ) => {
// eslint-disable-next-line no-console
const getShowCols = ( _headers: TableCardProps[ 'headers' ] = [] ) => {
return _headers
.map( ( { key, visible } ) => {
if ( typeof visible === 'undefined' || visible ) {
return key;
}
return false;
} )
.filter( Boolean ) as string[];
};
const [ showCols, setShowCols ] = useState( getShowCols( headers ) );
const onColumnToggle = ( key: string ) => {
return () => {
const hasKey = showCols.includes( key );
if ( hasKey ) {
// Handle hiding a sorted column
if ( query.orderby === key ) {
const defaultSort = find( headers, {
defaultSort: true,
} ) ||
first( headers ) || { key: undefined };
onQueryChange( 'sort' )( defaultSort.key, 'desc' );
}
const newShowCols = without( showCols, key );
onColumnsChange( newShowCols, key );
setShowCols( newShowCols );
} else {
const newShowCols = [ ...showCols, key ] as string[];
onColumnsChange( newShowCols, key );
setShowCols( newShowCols );
}
};
};
const onPageChange = (
newPage: string,
direction?: 'previous' | 'next'
) => {
if ( props.onPageChange ) {
props.onPageChange( parseInt( newPage, 10 ), direction );
}
if ( onQueryChange ) {
onQueryChange( 'paged' )( newPage, direction );
}
};
const allHeaders = headers;
const visibleHeaders = headers.filter( ( { key } ) =>
showCols.includes( key )
);
const visibleRows = rows.map( ( row ) => {
return headers
.map( ( { key }, i ) => {
return showCols.includes( key ) && row[ i ];
} )
.filter( Boolean );
} );
const classes = classnames( 'woocommerce-table', className, {
'has-actions': !! actions,
'has-menu': showMenu,
'has-search': hasSearch,
} );
return (
<Card className={ classes }>
<CardHeader>
<Text size={ 16 } weight={ 600 } as="h2" color="#23282d">
{ title }
</Text>
<div className="woocommerce-table__actions">{ actions }</div>
{ showMenu && (
<EllipsisMenu
label={ __(
'Choose which values to display',
'woocommerce'
) }
renderContent={ () => (
<Fragment>
{ /* @ts-expect-error: Ignoring the error until we migrate ellipsis-menu to TS*/ }
<MenuTitle>
{ /* @ts-expect-error: Allow string */ }
{ __( 'Columns:', 'woocommerce' ) }
</MenuTitle>
{ allHeaders.map(
( { key, label, required } ) => {
if ( required ) {
return null;
}
return (
<MenuItem
checked={ showCols.includes(
key
) }
isCheckbox
isClickable
key={ key }
onInvoke={
key !== undefined
? onColumnToggle( key )
: undefined
}
>
{ label }
</MenuItem>
);
}
) }
</Fragment>
) }
/>
) }
</CardHeader>
{ /* Ignoring the error to make it backward compatible for now. */ }
{ /* @ts-expect-error: size must be one of small, medium, largel, xSmall, extraSmall. */ }
<CardBody size={ null }>
{ isLoading ? (
<Fragment>
<span className="screen-reader-text">
{ __(
'Your requested data is loading',
'woocommerce'
) }
</span>
<TablePlaceholder
numberOfRows={ rowsPerPage }
headers={ visibleHeaders }
rowHeader={ rowHeader }
caption={ title }
query={ query }
/>
</Fragment>
) : (
<Table
rows={ visibleRows as TableCardProps[ 'rows' ] }
headers={
visibleHeaders as TableCardProps[ 'headers' ]
}
rowHeader={ rowHeader }
caption={ title }
query={ query }
onSort={
onSort ||
( onQueryChange( 'sort' ) as (
key: string,
direction: string
) => void )
}
rowKey={ rowKey }
emptyMessage={ emptyMessage }
/>
) }
</CardBody>
{ /* @ts-expect-error: justify is missing from the latest @types/wordpress__components */ }
<CardFooter justify="center">
{ isLoading ? (
<TableSummaryPlaceholder />
) : (
<Fragment>
<Pagination
key={ parseInt( query.paged as string, 10 ) || 1 }
page={ parseInt( query.paged as string, 10 ) || 1 }
perPage={ rowsPerPage }
total={ totalRows }
onPageChange={ onPageChange }
onPerPageChange={ onQueryChange( 'per_page' ) }
/>
{ summary && <TableSummary data={ summary } /> }
</Fragment>
) }
</CardFooter>
</Card>
);
};
export default TableCard;

View File

@ -1,68 +0,0 @@
/**
* External dependencies
*/
import { createElement, Component } from '@wordpress/element';
import { range } from 'lodash';
import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
import Table from './table';
/**
* `TablePlaceholder` behaves like `Table` but displays placeholder boxes instead of data. This can be used while loading.
*/
class TablePlaceholder extends Component {
render() {
const { numberOfRows, ...tableProps } = this.props;
const rows = range( numberOfRows ).map( () =>
this.props.headers.map( () => ( {
display: <span className="is-placeholder" />,
} ) )
);
return (
<Table
ariaHidden={ true }
className="is-loading"
rows={ rows }
{ ...tableProps }
/>
);
}
}
TablePlaceholder.propTypes = {
/**
* An object of the query parameters passed to the page, ex `{ page: 2, per_page: 5 }`.
*/
query: PropTypes.object,
/**
* A label for the content in this table.
*/
caption: PropTypes.string.isRequired,
/**
* An array of column headers (see `Table` props).
*/
headers: PropTypes.arrayOf(
PropTypes.shape( {
hiddenByDefault: PropTypes.bool,
defaultSort: PropTypes.bool,
isSortable: PropTypes.bool,
key: PropTypes.string,
label: PropTypes.node,
required: PropTypes.bool,
} )
),
/**
* An integer with the number of rows to display.
*/
numberOfRows: PropTypes.number,
};
TablePlaceholder.defaultProps = {
numberOfRows: 5,
};
export default TablePlaceholder;

View File

@ -0,0 +1,55 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { range } from 'lodash';
/**
* Internal dependencies
*/
import Table from './table';
import { QueryProps, TableHeader } from './types';
type TablePlaceholderProps = {
/** An object of the query parameters passed to the page */
query?: QueryProps;
/** A label for the content in this table. */
caption: string;
/** An integer with the number of rows to display. */
numberOfRows?: number;
/**
* Which column should be the row header, defaults to the first item (`0`) (but could be set to `1`, if the first col
* is checkboxes, for example). Set to false to disable row headers.
*/
rowHeader?: number | false;
/** An array of column headers (see `Table` props). */
headers: Array< TableHeader >;
};
/**
* `TablePlaceholder` behaves like `Table` but displays placeholder boxes instead of data. This can be used while loading.
*/
const TablePlaceholder: React.VFC< TablePlaceholderProps > = ( {
query,
caption,
headers,
numberOfRows = 5,
...props
} ) => {
const rows = range( numberOfRows ).map( () =>
headers.map( () => ( {
display: <span className="is-placeholder" />,
} ) )
);
const tableProps = { query, caption, headers, numberOfRows, ...props };
return (
<Table
ariaHidden={ true }
className="is-loading"
rows={ rows }
{ ...tableProps }
/>
);
};
export default TablePlaceholder;

View File

@ -2,6 +2,7 @@
* External dependencies
*/
import { EmptyTable } from '@woocommerce/components';
import { createElement } from '@wordpress/element';
export const Basic = () => <EmptyTable>There are no entries.</EmptyTable>;

View File

@ -1,42 +0,0 @@
/**
* External dependencies
*/
import { TableCard } from '@woocommerce/components';
import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import { headers, rows, summary } from './index';
const TableCardExample = () => {
const [ { query }, setState ] = useState( {
query: {
paged: 1,
},
} );
return (
<TableCard
title="Revenue last week"
rows={ rows }
headers={ headers }
onQueryChange={ ( param ) => ( value ) =>
setState( {
query: {
[ param ]: value,
},
} ) }
query={ query }
rowsPerPage={ 7 }
totalRows={ 10 }
summary={ summary }
/>
);
};
export const Basic = () => <TableCardExample />;
export default {
title: 'WooCommerce Admin/components/TableCard',
component: TableCard,
};

View File

@ -0,0 +1,93 @@
/**
* External dependencies
*/
import { TableCard } from '@woocommerce/components';
import { useState, createElement } from '@wordpress/element';
import { Button } from '@wordpress/components';
/**
* Internal dependencies
*/
import { headers, rows, summary } from './index';
const TableCardExample = () => {
const [ { query }, setState ] = useState( {
query: {
paged: 1,
},
} );
return (
<TableCard
title="Revenue last week"
rows={ rows }
headers={ headers }
onQueryChange={ ( param ) => ( value ) =>
setState( {
// @ts-expect-error: ignore for storybook
query: {
[ param ]: value,
},
} ) }
query={ query }
rowsPerPage={ 7 }
totalRows={ 10 }
summary={ summary }
/>
);
};
const TableCardWithActionsExample = () => {
const [ { query }, setState ] = useState( {
query: {
paged: 1,
},
} );
const [ action1Text, setAction1Text ] = useState( 'Action 1' );
const [ action2Text, setAction2Text ] = useState( 'Action 2' );
return (
<TableCard
actions={ [
<Button
key={ 0 }
onClick={ () => {
setAction1Text( 'Action 1 Clicked' );
} }
>
{ action1Text }
</Button>,
<Button
key={ 0 }
onClick={ () => {
setAction2Text( 'Action 2 Clicked' );
} }
>
{ action2Text }
</Button>,
] }
title="Revenue last week"
rows={ rows }
headers={ headers }
onQueryChange={ ( param ) => ( value ) =>
setState( {
// @ts-expect-error: ignore for storybook
query: {
[ param ]: value,
},
} ) }
query={ query }
rowsPerPage={ 7 }
totalRows={ 10 }
summary={ summary }
/>
);
};
export const Basic = () => <TableCardExample />;
export const Actions = () => <TableCardWithActionsExample />;
export default {
title: 'WooCommerce Admin/components/TableCard',
component: TableCard,
};

View File

@ -3,17 +3,21 @@
*/
import { Card } from '@wordpress/components';
import { TablePlaceholder } from '@woocommerce/components';
import { createElement } from '@wordpress/element';
/**
* Internal dependencies
*/
import { headers } from './index';
export const Basic = () => (
<Card size={ null }>
<TablePlaceholder caption="Revenue last week" headers={ headers } />
</Card>
);
export const Basic = () => {
return (
/* @ts-expect-error: size must be one of small, medium, largel, xSmall, extraSmall. */
<Card size={ null }>
<TablePlaceholder caption="Revenue last week" headers={ headers } />
</Card>
);
};
export default {
title: 'WooCommerce Admin/components/TablePlaceholder',

View File

@ -3,14 +3,18 @@
*/
import { Card, CardFooter } from '@wordpress/components';
import { TableSummaryPlaceholder } from '@woocommerce/components';
import { createElement } from '@wordpress/element';
export const Basic = () => (
<Card>
<CardFooter justify="center">
<TableSummaryPlaceholder />
</CardFooter>
</Card>
);
export const Basic = () => {
return (
<Card>
{ /* @ts-expect-error: justify is missing from the latest type def. */ }
<CardFooter justify="center">
<TableSummaryPlaceholder />
</CardFooter>
</Card>
);
};
export default {
title: 'WooCommerce Admin/components/TableSummaryPlaceholder',

View File

@ -3,6 +3,7 @@
*/
import { Card } from '@wordpress/components';
import { Table } from '@woocommerce/components';
import { createElement } from '@wordpress/element';
/**
* Internal dependencies
@ -20,17 +21,20 @@ export const Basic = () => (
</Card>
);
export const NoDataCustomMessage = () => (
<Card size={ null }>
<Table
caption="Revenue last week"
rows={ [] }
headers={ headers }
rowKey={ ( row ) => row[ 0 ].value }
emptyMessage="Custom empty message"
/>
</Card>
);
export const NoDataCustomMessage = () => {
return (
/* @ts-expect-error: size must be one of small, medium, largel, xSmall, extraSmall. */
<Card size={ null }>
<Table
caption="Revenue last week"
rows={ [] }
headers={ headers }
rowKey={ ( row ) => row[ 0 ].value }
emptyMessage="Custom empty message"
/>
</Card>
);
};
export default {
title: 'WooCommerce Admin/components/Table',

View File

@ -1,17 +1,17 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import { createElement } from '@wordpress/element';
/**
* A component to display summarized table data - the list of data passed in on a single line.
*
* @param {Object} props
* @param {Array} props.data
* @return {Object} -
* Internal dependencies
*/
const TableSummary = ( { data } ) => {
import { TableSummaryProps } from './types';
/**
* A component to display summarized table data - the list of data passed in on a single line.
*/
const TableSummary = ( { data }: TableSummaryProps ) => {
return (
<ul className="woocommerce-table__summary" role="complementary">
{ data.map( ( { label, value }, i ) => (
@ -28,13 +28,6 @@ const TableSummary = ( { data } ) => {
);
};
TableSummary.propTypes = {
/**
* An array of objects with `label` & `value` properties, which display on a single line.
*/
data: PropTypes.array,
};
export default TableSummary;
/**

Some files were not shown because too many files have changed in this diff Show More