diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec606dac31496..140fe4703d6de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2115,9 +2115,9 @@ importers: '@automattic/calypso-analytics': specifier: 1.1.2 version: 1.1.2 - '@automattic/components': - specifier: 2.1.1 - version: 2.1.1(@types/react@18.3.1)(@wordpress/data@10.2.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@automattic/calypso-color-schemes': + specifier: 3.1.3 + version: 3.1.3 '@automattic/i18n-utils': specifier: 1.2.3 version: 1.2.3 @@ -2133,6 +2133,9 @@ importers: '@automattic/viewport': specifier: 1.0.0 version: 1.0.0 + '@popperjs/core': + specifier: ^2.11.8 + version: 2.11.8 '@preact/signals': specifier: ^1.2.2 version: 1.3.0(preact@10.22.1) @@ -2181,9 +2184,18 @@ importers: clsx: specifier: 2.1.1 version: 2.1.1 + debug: + specifier: 4.3.4 + version: 4.3.4 preact: specifier: ^10.13.1 version: 10.22.1 + react-popper: + specifier: ^2.3.0 + version: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + redux: + specifier: ^4.2.1 + version: 4.2.1 wpcom-proxy-request: specifier: ^7.0.3 version: 7.0.5 @@ -2207,6 +2219,9 @@ importers: '@babel/preset-react': specifier: 7.24.7 version: 7.24.7(@babel/core@7.24.7) + '@babel/runtime': + specifier: ^7.24.5 + version: 7.24.7 '@playwright/test': specifier: 1.45.1 version: 1.45.1 @@ -4889,13 +4904,6 @@ packages: '@automattic/color-studio@2.6.0': resolution: {integrity: sha512-2LzB6bbQw1vayZxZy5Y+DnCYU7x8tPu+rZhNkWD7V8QZTSJMJO65XKZhYaCByC+C5OegXyGyZzcqEOHHdj5iiQ==} - '@automattic/components@2.1.1': - resolution: {integrity: sha512-PqGwe1CI0PtQIaTka5cbX/gKqEkhk4GYTdkExigHdzifnYtPZSmuLsw5mlPdfA8qY141s8wokARU7cDIZE0DCw==} - peerDependencies: - '@wordpress/data': ^9.26.0 - react: ^18.2.0 - react-dom: ^18.2.0 - '@automattic/explat-client-react-helpers@0.1.0': resolution: {integrity: sha512-p8yaBt3byJCIXQSNuw/oWnvu6khK8FFozPdQVnXCIbW+OO9pylBrZz+1VIzdPH7+0YjV0hRA0KGeiVp+uNCemw==} @@ -4905,30 +4913,15 @@ packages: '@automattic/format-currency@1.0.1': resolution: {integrity: sha512-RY2eiUlDiqNSHiJzz2YmH/mw4IjAUO5hkxbwcVGHJkBZowdq/WcSG2yhXc8N9cV9N1fTO/ryCuJvGnpHUe+mAg==} - '@automattic/format-currency@2.0.0': - resolution: {integrity: sha512-9A+oKRUm+n4f+cT4FHsDkCpo4mVRa/zBAvsXXq5vZpwfOWskAyDjdxA03Jl8A+z7pHYRimysG4WLM3jMRJutLw==} - '@automattic/i18n-utils@1.2.3': resolution: {integrity: sha512-zvZlazUoEasLATrta3ljfxu2uaZWgHRNKWf56KKBlrPiIxNQvx9D7YyN2MhiV27e/PuAhB0gI4ghqp3gzurKmA==} - '@automattic/interpolate-components@1.2.1': - resolution: {integrity: sha512-YNQtJsrs9KQ3lkBdtLyDheVRijoBA3y/PuHdgJ0eB4AX9JyjkDX7jd79Inh79+01CGNLbMQGrEJby2zvbJr17A==} - peerDependencies: - '@types/react': '>=16.14.23' - react: '>=16.2.0' - peerDependenciesMeta: - '@types/react': - optional: true - '@automattic/languages@1.0.0': resolution: {integrity: sha512-froTyDbTmLitHkvY9WLCpFdjUo6moOLkDKw63J2fLiB2gBApy2thkBV+LRx4Z0kIF5iXVkQF4yYOPYkT9Sr13Q==} '@automattic/load-script@1.0.0': resolution: {integrity: sha512-Hc1mRmTK12OKrONnGhe7Ht1Gpo4B/ls8WQ1IZ1/qBws1bUZ6u7Crnpv3HZkN4UI7irG3OU4l4Pn1TXtoJLcKRw==} - '@automattic/material-design-icons@1.0.0': - resolution: {integrity: sha512-8baJ1l8ftLq/UdLeucOeGXo4/wpaB/pSOBO587/pKC/xv2Oo8Ok21g1WKwp0Y8hEq4+3JNtCzOGVxmIgDBTYvA==} - '@automattic/popup-monitor@1.0.2': resolution: {integrity: sha512-Y4LMfdkV8iDmezu/7Ov/18JaFJ0QAy5vCntiP0S5AhLt4R/kjLtBt4ifNXNbdKTthGxlL17+LJ1bNtHBVCzPwg==} @@ -4948,11 +4941,6 @@ packages: '@automattic/typography@1.0.0': resolution: {integrity: sha512-TnT+vPaNUXQYwDsPCPxhNY0d4LnOKvrb0SizUCC5iybo5sfOlX/rYalGDyz6nPQDF0EBaQwMf7qhVsflFR0cBg==} - '@automattic/viewport-react@1.0.0': - resolution: {integrity: sha512-+6+l4jj14GXeoc5Jpic5E5eVvNL88Ezz8cMLmKAw0fpPDsz4gJv7o0hgShu0hjGjKTtBeUkBGfFWMCdRjZaVcA==} - peerDependencies: - react: ^16.0.0 - '@automattic/viewport@1.0.0': resolution: {integrity: sha512-aSJRuZ80kds6kO+baIw8wK4nmlHG/e/sUPa7BDwalo8vBLtBnW07GsXm1zGgz9htgEogFuuxBdKR4IjO1z8phA==} @@ -6322,6 +6310,9 @@ packages: engines: {node: '>=18'} hasBin: true + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@preact/signals-core@1.7.0': resolution: {integrity: sha512-bEZLgmJGSBVP5PUPDowhPW3bVdMmp9Tr5OEl+SQK+8Tv9T7UsIfyN905cfkmmeqw8z4xp8T6zrl4M1uj9+HAfg==} @@ -7205,9 +7196,6 @@ packages: '@tannin/postfix@1.1.0': resolution: {integrity: sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw==} - '@tannin/sprintf@1.2.0': - resolution: {integrity: sha512-T0ORaQrH6kNFGzTg285RVPK+NCYZxOoA+r0QfKgHqK+yk5RuYPSKDa18XCLtycCNq+VWKpfyDpzGUGhYgCV+kw==} - '@tanstack/query-core@4.35.3': resolution: {integrity: sha512-PS+WEjd9wzKTyNjjQymvcOe1yg8f3wYc6mD+vb6CKyZAKvu4sIJwryfqfBULITKCla7P9C4l5e9RXePHvZOZeQ==} @@ -7492,9 +7480,6 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - '@types/react-dom@16.9.24': - resolution: {integrity: sha512-Gcmq2JTDheyWn/1eteqyzzWKSqDjYU6KYsIvH7thb7CR5OYInAWOX+7WnKf6PaU/cbdOc4szJItcDEJO7UGmfA==} - '@types/react-dom@18.3.0': resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} @@ -7513,9 +7498,6 @@ packages: '@types/react-test-renderer@18.3.0': resolution: {integrity: sha512-HW4MuEYxfDbOHQsVlY/XtOvNHftCVEPhJF2pQXXwcUiUF+Oyb0usgp48HSgpK5rt8m9KZb22yqOeZm+rrVG8gw==} - '@types/react@16.14.60': - resolution: {integrity: sha512-wIFmnczGsTcgwCBeIYOuy2mdXEiKZ5znU/jNOnMZPQyCcIxauMGWlX0TNG4lZ7NxRKj7YUIZRneJQSSdB2jKgg==} - '@types/react@18.3.1': resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} @@ -7528,9 +7510,6 @@ packages: '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} - '@types/scheduler@0.16.8': - resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} - '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -7759,10 +7738,6 @@ packages: webpack-dev-server: optional: true - '@wordpress/a11y@3.58.0': - resolution: {integrity: sha512-7NnJKl4+pxP6kV/jvXaJcZZCGzW7zaj6YeMnyjUd96cH4ta1ykBIveWgejerFOGsbK+88FnStcxSFj+dbDXs/w==} - engines: {node: '>=12'} - '@wordpress/a11y@4.2.0': resolution: {integrity: sha512-IwW1mf4zIo/BIa19MB4yGLWQl3MlzB0tu+0Coct+m0gc7uaFuSov2ub56vpDvXvNsfLISWtvWBFL4TQTInNdiA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7791,9 +7766,6 @@ packages: resolution: {integrity: sha512-bOvP+8b0zk7wjIbpaAnJSc0kkKZA6oeyJ4/GRtp+mmcrIXJWL+LNCGYQM+aUDSGO7j1QazLpTR7p5br8ygl5/g==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/base-styles@4.49.0': - resolution: {integrity: sha512-yFRYqNtd26ULZ0oAHhCu/IcaA0XHI3E7kRCKajZqUvyRQj7YprXnpD3o0/pnwvF6ZFTXzCX8pXHjUc2TIv97ig==} - '@wordpress/base-styles@5.2.0': resolution: {integrity: sha512-yBMVbn4gvNQimVfLAZ+/F7F/Tpl+femF9ojgv90c0A0o6IDEtdC+6vUvtAxvXVoruwmxsq8ncouoorZbYDb3yg==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7837,13 +7809,6 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/components@27.6.0': - resolution: {integrity: sha512-f+fXENkgrPs5GLo2yu9fEAdVX0KriEatRcjDUyw0+DbNbJR62sCdDtGdhJRW4jPUUoUowxaGO0y4+jvQWxnbyg==} - engines: {node: '>=12'} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - '@wordpress/components@28.2.0': resolution: {integrity: sha512-6yq1D4SrS5i9lvukCiN+FtKXh42JUV+T0X+e7xI3qFANaRMqjSNqEJFOu14H2oKIbZwjdKxEJ/Wck1lWBIKu/A==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7851,15 +7816,6 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/compose@3.25.3': - resolution: {integrity: sha512-tCO2EnJCkCH548OqA0uU8V1k/1skz2QwBlHs8ZQSpimqUS4OWWsAlndCEFe4U4vDTqFt2ow7tzAir+05Cw8MAg==} - - '@wordpress/compose@6.35.0': - resolution: {integrity: sha512-PfruhCxxxJokDQHc2YBgerEiHV7BIxQk9g5vU4/f9X/0PBQWUTuxOzSFcAba03vnjfAgtPTSMp50T50hcJwXfA==} - engines: {node: '>=12'} - peerDependencies: - react: ^18.0.0 - '@wordpress/compose@7.2.0': resolution: {integrity: sha512-J2OGEatXXTgRJmXZHYcstL5GyQgQcoeSJ9dQ2wVFHJLnWoIX3hlvi5oRy10lpl1yntfm6NLkWDBuSTIbAYJzww==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7886,16 +7842,6 @@ packages: peerDependencies: react: ^18.0.0 - '@wordpress/data@9.28.0': - resolution: {integrity: sha512-EDPpZdkngdoW7EMzPpGj0BmNcr7syJO67pgTODtN/4XFIdYL2RKzFyn3nlLBKhX17UsE/ALq9WdijacH4QJ9qw==} - engines: {node: '>=12'} - peerDependencies: - react: ^18.0.0 - - '@wordpress/date@4.58.0': - resolution: {integrity: sha512-yFT7DU0H9W0lsDytMaVMmjho08X1LeBMIQMppxdtKB04Ujx58hVh7gtunOsstUQ7pVg23nE2eLaVfx5JOdjzAw==} - engines: {node: '>=12'} - '@wordpress/date@5.2.0': resolution: {integrity: sha512-k9gFs74hg9yvrWhoUrNrCf95VIqvPkv2AysRxTxSfEZjTyMBuxNNa3amr295hCeoqcjT9mw5bptmLntawmg7nA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7906,32 +7852,14 @@ packages: peerDependencies: webpack: ^5.0.0 - '@wordpress/deprecated@2.12.3': - resolution: {integrity: sha512-qr+yDfTQfI3M4h6oY6IeHWwoHr4jxbILjSlV+Ht6Jjto9Owap6OuzSqR13Ev4xqIoG4C7b5B3gZXVfwVDae1zg==} - - '@wordpress/deprecated@3.58.0': - resolution: {integrity: sha512-knweE2lLEUxWRr6A48sHiO0ww5pPybGe2NVIZVq/y7EaYCMdpy6gYA0ZdVqMKZvtxKKqicJfwigcn+hinsTvUQ==} - engines: {node: '>=12'} - '@wordpress/deprecated@4.2.0': resolution: {integrity: sha512-grD/IBGEvXzTLaNB45QZv8jDQYK6bMhCSa7obRahZDpyrM2lwKwa8p1oack5R+81YWkBL02gl9V5G9BrpNfBJg==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/dom-ready@3.58.0': - resolution: {integrity: sha512-sDgRPjNBToRKgYrpwvMRv2Yc7/17+sp8hm/rRnbubwb+d/DbGkK4Tc/r4sNLSZCqUAtcBXq9uk1lzvhge3QUSg==} - engines: {node: '>=12'} - '@wordpress/dom-ready@4.2.0': resolution: {integrity: sha512-1rD9vcwVy7s+yGbe8DuDkBpjvA/PJtY2DnUgyHIedC/YonlswalBSiFWGZuTX5QyocdffBAWiUlvebyN4TNtOw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/dom@2.18.0': - resolution: {integrity: sha512-tM2WeQuSObl3nzWjUTF0/dyLnA7sdl/MXaSe32D64OF89bjSyJvjUipI7gjKzI3kJ7ddGhwcTggGvSB06MOoCQ==} - - '@wordpress/dom@3.58.0': - resolution: {integrity: sha512-t3xSr/nqekj2qwUGRAqSeGx6116JOBxzI+VBiUfZrjGEnuyKdLelXDEeYtcwbb7etMkj/6F60/NB7GTl5IwizQ==} - engines: {node: '>=12'} - '@wordpress/dom@4.2.0': resolution: {integrity: sha512-vkeIsFdoKWl6lZJM+E49b+HocePP8gSPiDeUaa3P82JPTLTNAAfQMGWbAG0dbQCOZa7pmF4Sh0T1iVYmznm6eA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7950,24 +7878,10 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/element@2.20.3': - resolution: {integrity: sha512-f4ZPTDf9CxiiOXiMxc4v1K7jcBMT4dsiehVOpkKzCDKboNXp4qVf8oe5PE23VGZNEjcOj5Mkg9hB57R0nqvMTw==} - - '@wordpress/element@5.35.0': - resolution: {integrity: sha512-puswpGcIdS+0A2g28uHriMkZqqRCmzFczue5Tk99VNtzBdehyk7Ae+DZ4xw5yT6GqYai8NTqv6MRwCB78uh5Mw==} - engines: {node: '>=12'} - '@wordpress/element@6.2.0': resolution: {integrity: sha512-pRCchhYoH7eN0bxL4iUMBm82psqSUozlmk4B5IhQiqzYoOWn7OjvkGqejAnt81iDZUNZ8hIY2gLpRplgwwiZlQ==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/escape-html@1.12.2': - resolution: {integrity: sha512-FabgSwznhdaUwe6hr1CsGpgxQbzqEoGevv73WIL1B9GvlZ6csRWodgHfWh4P6fYqpzxFL4WYB8wPJ1PdO32XFA==} - - '@wordpress/escape-html@2.58.0': - resolution: {integrity: sha512-9YJXMNfzkrhHEVP1jFEhgijbZqW8Mt3NHIMZjIQoWtBf7QE86umpYpGGBXzYC0YlpGTRGzZTBwYaqFKxjeaSgA==} - engines: {node: '>=12'} - '@wordpress/escape-html@3.2.0': resolution: {integrity: sha512-GFJ91lrs46zN3bgRGBHREaZ4jegwUA+2Gx+P6f11VDLhihNGKyg67uNf0lXqLoLj6iQQCBDP+15k/0k2ccr3YA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8003,34 +7917,14 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/hooks@2.12.3': - resolution: {integrity: sha512-LmKiwKldZt6UYqOxV/a6+eUFXdvALFnB/pQx3RmrMvO64sgFhfR6dhrlv+uVbuuezSuv8dce1jx8lUWAT0krMA==} - - '@wordpress/hooks@3.58.0': - resolution: {integrity: sha512-9LB0ZHnZRQlORttux9t/xbAskF+dk2ujqzPGsVzc92mSKpQP3K2a5Wy74fUnInguB1vLUNHT6nrNdkVom5qX1Q==} - engines: {node: '>=12'} - '@wordpress/hooks@4.2.0': resolution: {integrity: sha512-N2dRMIb3F6y2dXlcT6m2CH/jDi9/Fe0gaM6ev7DrvwJ8+kX1CRzwAydemmPw34EnhQKvYKQYgGqttrfzvzgKJw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/html-entities@3.58.0': - resolution: {integrity: sha512-FU7b6QZdwTCuLKq6wCl0IZqqOMcMRxMcekVVytzTse7hYk9dvL1qQL/U4eQ/CNyKqiT9u7fb5NKTQILOzoolVQ==} - engines: {node: '>=12'} - '@wordpress/html-entities@4.2.0': resolution: {integrity: sha512-NwYv/VSxASrhIqjcgVkKS9s9kAseTJ6NYVpqCm8owz81GqYcpOe2XVCKQxOKHcU8x9Gm99uHR10jztXXJr1Z2Q==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/i18n@3.20.0': - resolution: {integrity: sha512-SIoOJFB4UrrYAScS4H91CYCLW9dX3Ghv8pBKc/yHGculb1AdGr6gRMlmJxZV62Cn3CZ4Ga86c+FfR+GiBu0JPg==} - hasBin: true - - '@wordpress/i18n@4.58.0': - resolution: {integrity: sha512-VfvS3BWv/RDjRKD6PscIcvYfWKnGJcI/DEqyDgUMhxCM6NRwoL478CsUKTiGJIymeyRodNRfprdcF086DpGKYw==} - engines: {node: '>=12'} - hasBin: true - '@wordpress/i18n@5.2.0': resolution: {integrity: sha512-G8z3/o1gm158XHANzthMBLsInQ/iWBFFIUoThiOP8C+VtpVozVpmWpgdayldPoTYolhCdaW6dicNUfdX8fOBTQ==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8042,12 +7936,6 @@ packages: peerDependencies: react: ^18 - '@wordpress/icons@9.49.0': - resolution: {integrity: sha512-Z8F+ledkfkcKDuS1c/RkM0dEWdfv2AXs6bCgey89p0atJSscf7qYbMJR9zE5rZ5aqXyFfV0DAFKJEgayNqneNQ==} - engines: {node: '>=12'} - peerDependencies: - react: ^18 - '@wordpress/interactivity-router@2.2.0': resolution: {integrity: sha512-HWWE+U/7hWiH2dopIcx93MraohheKMFIlWVN5CiLqcUsUp5aKiOJXyhGhPvau4qnv3Y+Tm05sTt9ZFlIPmIoeg==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8063,13 +7951,6 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/is-shallow-equal@3.1.3': - resolution: {integrity: sha512-eDLhfC4aaSgklzqwc6F/F4zmJVpTVTAvhqX+q0SP/8LPcP2HuKErPHVrEc75PMWqIutja2wJg98YSNPdewrj1w==} - - '@wordpress/is-shallow-equal@4.58.0': - resolution: {integrity: sha512-NH2lbXo/6ix1t4Zu9UBXpXNtoLwSaYmIRSyDH34XNb0ic8a7yjEOhYWVW3LTfSCv9dJVyxlM5TJPtL85q7LdeQ==} - engines: {node: '>=12'} - '@wordpress/is-shallow-equal@5.2.0': resolution: {integrity: sha512-WIsaAu+vDoAwnfGSWqyOMZiJeKXMXHNj6SzuESieASbL1VcbWgpg8FjDRjCNCEBgu7oe2VQrDOpDfajI1fgVvw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8086,13 +7967,6 @@ packages: peerDependencies: react: ^18.0.0 - '@wordpress/keycodes@2.19.3': - resolution: {integrity: sha512-8rNdmP5M1ifTgLIL0dt/N1uTGsq/Rx1ydCXy+gg24WdxBRhyu5sudNVCtascVXo26aIfOH9OJRdqRZZTEORhog==} - - '@wordpress/keycodes@3.58.0': - resolution: {integrity: sha512-Q/LRKpx8ndzuHlkxSQ2BD+NTYYKQPIneNNMng8hTAfyU7RFwXpqj06HpeOFGh4XIdPKCs/8hmucoLJRmmLmZJA==} - engines: {node: '>=12'} - '@wordpress/keycodes@4.2.0': resolution: {integrity: sha512-FJMR+KLfltcfmd0GhpI2C+zohFaGwPwZTYx9e0+cnIDJ5c5Jw1KTToZ+gkWPoSi++U/dlqkxkKYLD4AnGg7L5Q==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8140,47 +8014,20 @@ packages: peerDependencies: prettier: '>=3' - '@wordpress/primitives@3.56.0': - resolution: {integrity: sha512-NXBq1ODjl6inMWx/l7KCbATcjdoeIOqYeL9i9alqdAfWeKx1EH9PIvKWylIkqZk7erXxCxldiRkuyjTtwjNBxw==} - engines: {node: '>=12'} - peerDependencies: - react: ^18 - '@wordpress/primitives@4.2.0': resolution: {integrity: sha512-UofDIMe3pQ4UvubCAjm4/Y+o/niAiHFRjhavvxBOZ5iCFyjG/1knbJcWa8+0qvjIA5YTBzZxfN6PD4Je1SwtFw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} peerDependencies: react: ^18 - '@wordpress/priority-queue@1.11.2': - resolution: {integrity: sha512-ulwmUOklY3orn1xXpcPnTyGWV5B/oycxI+cHZ6EevBVgM5sq+BW3xo0PKLR/MMm6UNBtFTu/71QAJrNZcD6V1g==} - - '@wordpress/priority-queue@2.58.0': - resolution: {integrity: sha512-W+qCS8HJWsXG8gE6yK/H/IObowcghPrQMM3cQHtfd/U05yFNU1Bd/fbj3AO1fVRztktS47lIpi9m3ll1evPEHA==} - engines: {node: '>=12'} - '@wordpress/priority-queue@3.2.0': resolution: {integrity: sha512-Kz/Zv+/TzgsKi5M3/iE2w4sMSi0f2Q3KnmU6taS5bEiiKRHvuC1U629YBsXCvBfi+7QWe2L7J7OVcLRwdEzAkg==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/private-apis@0.40.0': - resolution: {integrity: sha512-ZX/9Y8eA3C3K6LOj32bHFj+9tNV819CBd8+chqMmmlvQRcTngiuXbMbnSdZnnAr1gLQgNpH9PJ60dIwJnGSEtQ==} - engines: {node: '>=12'} - '@wordpress/private-apis@1.2.0': resolution: {integrity: sha512-N0eRg0IHbgg1G8Z5/UCmU/FNan+YEAkVauM0W8OPp2/jeqQHS70VlrGwzmF+I8o2yInI6gL38dPE8sYElNiBFA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/react-i18n@3.56.0': - resolution: {integrity: sha512-Qe7EDCazhhrBLsvqJOYZdIygamJFJQbZGvhBD/9O7H/PgLbrxIluRqTiY1Vo+hN6W6vTIuaOcXlCxZKEmSGC0A==} - engines: {node: '>=12'} - - '@wordpress/redux-routine@4.58.0': - resolution: {integrity: sha512-r0mMWFeJr93yPy2uY/M5+gdUUYj0Zu8+21OFFb5hyQ0z7UHIa3IdgQxzCaTbV1LDA1ZYJrjHeCnA6s4gNHjA2Q==} - engines: {node: '>=12'} - peerDependencies: - redux: '>=4' - '@wordpress/redux-routine@5.2.0': resolution: {integrity: sha512-tzUQeO8sjkOn1l5MlrzXwjFhkEYNpw21nQ/sQP/smQp70rG22hPZSYOK8/C5ls1KGbzCtfwv92NxwJEKbBypiw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8194,12 +8041,6 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/rich-text@6.35.0': - resolution: {integrity: sha512-h6/XftSqo9UQZebuNZyLfOVu+ButBLITW/BILsKeJhSpmM19VNdz8UhVGLp+xQPE+/GPCIMJrhhqipISDfc2Ig==} - engines: {node: '>=12'} - peerDependencies: - react: ^18.0.0 - '@wordpress/rich-text@7.2.0': resolution: {integrity: sha512-cjp/Y4bPavge1dV2JneM8km3Hq166BJLPPRpOpw9wBI4xlyjgyXHnMX6tqoU/nMIBXq4asWQrXQfkzbA0iJxcA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8235,10 +8076,6 @@ packages: resolution: {integrity: sha512-4CDvT2x+NmP255E4JSowpHiZUK2wqVOvfrcuY884mm149+ZmPp2OyGuRnn4aqFEs3+Bcxjg5NEz0o4KH0CXt5Q==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/undo-manager@0.18.0': - resolution: {integrity: sha512-upbzPEToa095XG+2JXLHaolF1LfXEMFS0lNMYV37myoUS+eZ7/tl9Gx+yU2+OqWy57TMwx33NlWUX/n+ynzPRw==} - engines: {node: '>=12'} - '@wordpress/undo-manager@1.2.0': resolution: {integrity: sha512-xOjyl2hRro5I3pBuDYvrF+cLe0617KlPiuJok9YPofjUvfDvgf6gPLSBOAPj2GzYkiGASz0fnsPcE4UxS14ApQ==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8253,10 +8090,6 @@ packages: peerDependencies: react: ^18.0.0 - '@wordpress/warning@2.58.0': - resolution: {integrity: sha512-9bZlORhyMY2nbWozeyC5kqJsFzEPP4DCLhGmjtbv+YWGHttUrxUZEfrKdqO+rUODA8rP5zeIly1nCQOUnkw4Lg==} - engines: {node: '>=12'} - '@wordpress/warning@3.2.0': resolution: {integrity: sha512-i+EYX506tE45+lhOW4qAwXm/5/PmkAdvKhNt+dTxJDif+ATUqp4QaFFRl9yFIt/v+ksAapQ0bnPkKNSDsECGtA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8835,9 +8668,6 @@ packages: caniuse-lite@1.0.30001640: resolution: {integrity: sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==} - canvas-confetti@1.9.3: - resolution: {integrity: sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==} - capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} @@ -8937,9 +8767,6 @@ packages: cjs-module-lexer@1.3.1: resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} - classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - clean-css@5.3.3: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} @@ -10088,9 +9915,6 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} - exenv@1.2.2: - resolution: {integrity: sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==} - exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} @@ -10544,11 +10368,6 @@ packages: peerDependencies: react: 15 - 18 - gridicons@3.4.2: - resolution: {integrity: sha512-KC2BzPDh3F0vJzYa7KYBWJOO9gTHoKoFiHNazZEU9Gq2jIJ2zObOA67wlZjZkPHPCjZiLQrko3AYFLrMrHXKrA==} - peerDependencies: - react: 15 - 18 - gunzip-maybe@1.4.2: resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} hasBin: true @@ -10714,11 +10533,6 @@ packages: engines: {node: '>=14'} hasBin: true - i18n-calypso@7.0.0: - resolution: {integrity: sha512-GQesQzd/VYXiJOrjMixJNFOqNOcp43kKGKZTimYu70RabvcObpjfAOqtrQganszXqXWxZ7fAXOnhCTd8NVtf/Q==} - peerDependencies: - react: ^18.2.0 - iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -11627,10 +11441,6 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lru@3.1.0: - resolution: {integrity: sha512-5OUtoiVIGU4VXBOshidmtOsvBIvcQR6FD/RzWSvaeHyxCGB+PCUCu+52lqMfdc0h/2CLvHhZS4TwUmMQrrMbBQ==} - engines: {node: '>= 0.4.0'} - lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -11736,9 +11546,6 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} - memize@1.1.0: - resolution: {integrity: sha512-K4FcPETOMTwe7KL2LK0orMhpOmWD2wRGwWWpbZy0fyArwsyIKR8YJVz8+efBAh3BO4zPqlSICu4vsLTRRqtFAg==} - memize@2.1.0: resolution: {integrity: sha512-yywVJy8ctVlN5lNPxsep5urnZ6TTclwPEyigM9M3Bi8vseJBOfqNrGWN/r8NzuIt3PovM323W04blJfGQfQSVg==} @@ -12939,11 +12746,6 @@ packages: resolution: {integrity: sha512-i8aF1nyKInZnANZ4uZrH49qn1paRgBZ7wZiCNBMnenlPzEv0mRl+ShpTVEI6wZNl8sSc79xZkivtgLKQArcanQ==} engines: {node: '>=16.14.0'} - react-dom@16.14.0: - resolution: {integrity: sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==} - peerDependencies: - react: ^16.14.0 - react-dom@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -12966,6 +12768,9 @@ packages: react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -12978,22 +12783,19 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-lifecycles-compat@3.0.4: - resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} - - react-modal@3.16.1: - resolution: {integrity: sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==} - engines: {node: '>=8'} - peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 - react-dom: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 - react-page-visibility@7.0.0: resolution: {integrity: sha512-d4Kq/8TtJSr8dQc8EJeAZcSKTrGzC5OPTm6UrMur9BnwP0fgTawI9+Nd+ZGB7vwCfn2yZS0qDF9DR3/QYTGazw==} engines: {node: '>=10'} peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-popper@2.3.0: + resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==} + peerDependencies: + '@popperjs/core': ^2.0.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + react-redux@7.2.8: resolution: {integrity: sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==} peerDependencies: @@ -13036,11 +12838,6 @@ packages: '@types/react': optional: true - react-resize-aware@3.1.3: - resolution: {integrity: sha512-dPacJZfDczrGOljY8MBnQRudxjI8+M9qG5GXQzU4wIl5q2T4e8UcrSl2rsEBYn9DBuGflhDnI2uYGGFJ991DfA==} - peerDependencies: - react: ^16.8.0 || 17.x || 18.x - react-router-dom@5.3.4: resolution: {integrity: sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==} peerDependencies: @@ -13152,10 +12949,6 @@ packages: react: '>=16.13' react-dom: '>=16.13' - react@16.14.0: - resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==} - engines: {node: '>=0.10.0'} - react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -13474,9 +13267,6 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.19.1: - resolution: {integrity: sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==} - scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -14422,11 +14212,6 @@ packages: '@types/react': optional: true - use-subscription@1.6.0: - resolution: {integrity: sha512-0Y/cTLlZfw547tJhJMoRA16OUbVqRm6DmvGpiGbmLST6BIA5KU5cKlvlz8DVMrACnWpyEjCkgmhLatthP4jUbA==} - peerDependencies: - react: ^18.0.0 - use-sync-external-store@1.2.2: resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} peerDependencies: @@ -14441,10 +14226,6 @@ packages: utila@0.4.0: resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} - utility-types@3.11.0: - resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} - engines: {node: '>= 4'} - utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -14904,39 +14685,6 @@ snapshots: '@automattic/color-studio@2.6.0': {} - '@automattic/components@2.1.1(@types/react@18.3.1)(@wordpress/data@10.2.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@automattic/calypso-analytics': 1.1.2 - '@automattic/calypso-url': 1.1.0 - '@automattic/format-currency': 2.0.0 - '@automattic/i18n-utils': 1.2.3 - '@automattic/material-design-icons': 1.0.0 - '@automattic/typography': 1.0.0 - '@automattic/viewport-react': 1.0.0(react@18.3.1) - '@wordpress/base-styles': 4.49.0 - '@wordpress/components': 27.6.0(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@wordpress/data': 10.2.0(react@18.3.1) - '@wordpress/icons': 9.49.0(react@18.3.1) - '@wordpress/react-i18n': 3.56.0 - canvas-confetti: 1.9.3 - classnames: 2.5.1 - gridicons: 3.4.2(react@18.3.1) - i18n-calypso: 7.0.0(@types/react@18.3.1)(react@18.3.1) - lodash: 4.17.21 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-modal: 3.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router-dom: 6.21.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-slider: 2.0.5(react@18.3.1) - utility-types: 3.11.0 - uuid: 9.0.1 - transitivePeerDependencies: - - '@babel/runtime' - - '@emotion/is-prop-valid' - - '@types/react' - - supports-color - '@automattic/explat-client-react-helpers@0.1.0': dependencies: '@automattic/explat-client': 0.1.0 @@ -14951,10 +14699,6 @@ snapshots: dependencies: tslib: 2.5.0 - '@automattic/format-currency@2.0.0': - dependencies: - tslib: 2.6.3 - '@automattic/i18n-utils@1.2.3': dependencies: '@automattic/calypso-config': 1.2.0 @@ -14967,12 +14711,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@automattic/interpolate-components@1.2.1(@types/react@18.3.1)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.1 - '@automattic/languages@1.0.0': dependencies: tslib: 2.5.0 @@ -14984,8 +14722,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@automattic/material-design-icons@1.0.0': {} - '@automattic/popup-monitor@1.0.2': dependencies: events: 3.3.0 @@ -15015,12 +14751,6 @@ snapshots: '@automattic/typography@1.0.0': {} - '@automattic/viewport-react@1.0.0(react@18.3.1)': - dependencies: - '@automattic/viewport': 1.0.0 - '@wordpress/compose': 3.25.3(react@18.3.1) - react: 18.3.1 - '@automattic/viewport@1.0.0': {} '@automattic/webpack-rtl-plugin@6.0.0(webpack@5.76.0(webpack-cli@4.9.1))': @@ -16644,6 +16374,8 @@ snapshots: dependencies: playwright: 1.45.1 + '@popperjs/core@2.11.8': {} + '@preact/signals-core@1.7.0': {} '@preact/signals@1.3.0(preact@10.22.1)': @@ -18519,8 +18251,6 @@ snapshots: '@tannin/postfix@1.1.0': {} - '@tannin/sprintf@1.2.0': {} - '@tanstack/query-core@4.35.3': {} '@tanstack/query-core@5.0.5': {} @@ -18824,10 +18554,6 @@ snapshots: '@types/range-parser@1.2.7': {} - '@types/react-dom@16.9.24': - dependencies: - '@types/react': 16.14.60 - '@types/react-dom@18.3.0': dependencies: '@types/react': 18.3.1 @@ -18837,7 +18563,7 @@ snapshots: '@types/hoist-non-react-statics': 3.3.5 '@types/react': 18.3.1 hoist-non-react-statics: 3.3.2 - redux: 4.1.1 + redux: 4.2.1 '@types/react-router-dom@5.3.3': dependencies: @@ -18858,12 +18584,6 @@ snapshots: dependencies: '@types/react': 18.3.1 - '@types/react@16.14.60': - dependencies: - '@types/prop-types': 15.7.12 - '@types/scheduler': 0.16.8 - csstype: 3.1.3 - '@types/react@18.3.1': dependencies: '@types/prop-types': 15.7.12 @@ -18877,8 +18597,6 @@ snapshots: '@types/retry@0.12.0': {} - '@types/scheduler@0.16.8': {} - '@types/semver@7.5.8': {} '@types/send@0.17.4': @@ -19219,12 +18937,6 @@ snapshots: dependencies: webpack-cli: 4.9.1(webpack@5.76.0) - '@wordpress/a11y@3.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/dom-ready': 3.58.0 - '@wordpress/i18n': 4.58.0 - '@wordpress/a11y@4.2.0': dependencies: '@babel/runtime': 7.24.7 @@ -19271,8 +18983,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@wordpress/base-styles@4.49.0': {} - '@wordpress/base-styles@5.2.0': {} '@wordpress/blob@4.2.0': @@ -19575,62 +19285,6 @@ snapshots: - '@types/react' - supports-color - '@wordpress/components@27.6.0(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@ariakit/react': 0.3.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@babel/runtime': 7.24.7 - '@emotion/cache': 11.11.0 - '@emotion/css': 11.11.2 - '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.3.1) - '@emotion/serialize': 1.1.4 - '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1) - '@emotion/utils': 1.2.1 - '@floating-ui/react-dom': 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/gradient-parser': 0.1.3 - '@types/highlight-words-core': 1.2.1 - '@use-gesture/react': 10.3.1(react@18.3.1) - '@wordpress/a11y': 3.58.0 - '@wordpress/compose': 6.35.0(react@18.3.1) - '@wordpress/date': 4.58.0 - '@wordpress/deprecated': 3.58.0 - '@wordpress/dom': 3.58.0 - '@wordpress/element': 5.35.0 - '@wordpress/escape-html': 2.58.0 - '@wordpress/hooks': 3.58.0 - '@wordpress/html-entities': 3.58.0 - '@wordpress/i18n': 4.58.0 - '@wordpress/icons': 9.49.0(react@18.3.1) - '@wordpress/is-shallow-equal': 4.58.0 - '@wordpress/keycodes': 3.58.0 - '@wordpress/primitives': 3.56.0(react@18.3.1) - '@wordpress/private-apis': 0.40.0 - '@wordpress/rich-text': 6.35.0(react@18.3.1) - '@wordpress/warning': 2.58.0 - change-case: 4.1.2 - clsx: 2.1.1 - colord: 2.9.3 - date-fns: 3.6.0 - deepmerge: 4.3.1 - downshift: 6.1.12(react@18.3.1) - fast-deep-equal: 3.1.3 - framer-motion: 11.2.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - gradient-parser: 0.1.5 - highlight-words-core: 1.2.2 - is-plain-object: 5.0.0 - memize: 2.1.0 - path-to-regexp: 6.2.2 - re-resizable: 6.9.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-colorful: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-dom: 18.3.1(react@18.3.1) - remove-accents: 0.5.0 - use-lilius: 2.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - uuid: 9.0.1 - transitivePeerDependencies: - - '@emotion/is-prop-valid' - - '@types/react' - - supports-color - '@wordpress/components@28.2.0(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@ariakit/react': 0.3.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -19743,41 +19397,6 @@ snapshots: - '@types/react' - supports-color - '@wordpress/compose@3.25.3(react@18.3.1)': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/deprecated': 2.12.3 - '@wordpress/dom': 2.18.0 - '@wordpress/element': 2.20.3 - '@wordpress/is-shallow-equal': 3.1.3 - '@wordpress/keycodes': 2.19.3 - '@wordpress/priority-queue': 1.11.2 - clipboard: 2.0.11 - lodash: 4.17.21 - memize: 1.1.0 - mousetrap: 1.6.5 - react-resize-aware: 3.1.3(react@18.3.1) - use-memo-one: 1.1.3(react@18.3.1) - transitivePeerDependencies: - - react - - '@wordpress/compose@6.35.0(react@18.3.1)': - dependencies: - '@babel/runtime': 7.24.7 - '@types/mousetrap': 1.6.15 - '@wordpress/deprecated': 3.58.0 - '@wordpress/dom': 3.58.0 - '@wordpress/element': 5.35.0 - '@wordpress/is-shallow-equal': 4.58.0 - '@wordpress/keycodes': 3.58.0 - '@wordpress/priority-queue': 2.58.0 - '@wordpress/undo-manager': 0.18.0 - change-case: 4.1.2 - clipboard: 2.0.11 - mousetrap: 1.6.5 - react: 18.3.1 - use-memo-one: 1.1.3(react@18.3.1) - '@wordpress/compose@7.2.0(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 @@ -19960,32 +19579,6 @@ snapshots: rememo: 4.0.2 use-memo-one: 1.1.3(react@18.3.1) - '@wordpress/data@9.28.0(react@18.3.1)': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/compose': 6.35.0(react@18.3.1) - '@wordpress/deprecated': 3.58.0 - '@wordpress/element': 5.35.0 - '@wordpress/is-shallow-equal': 4.58.0 - '@wordpress/priority-queue': 2.58.0 - '@wordpress/private-apis': 0.40.0 - '@wordpress/redux-routine': 4.58.0(redux@4.2.1) - deepmerge: 4.3.1 - equivalent-key-map: 0.2.2 - is-plain-object: 5.0.0 - is-promise: 4.0.0 - react: 18.3.1 - redux: 4.2.1 - rememo: 4.0.2 - use-memo-one: 1.1.3(react@18.3.1) - - '@wordpress/date@4.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/deprecated': 3.58.0 - moment: 2.29.4 - moment-timezone: 0.5.45 - '@wordpress/date@5.2.0': dependencies: '@babel/runtime': 7.24.7 @@ -19998,39 +19591,15 @@ snapshots: json2php: 0.0.7 webpack: 5.76.0(webpack-cli@4.9.1) - '@wordpress/deprecated@2.12.3': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/hooks': 2.12.3 - - '@wordpress/deprecated@3.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/hooks': 3.58.0 - '@wordpress/deprecated@4.2.0': dependencies: '@babel/runtime': 7.24.7 '@wordpress/hooks': 4.2.0 - '@wordpress/dom-ready@3.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/dom-ready@4.2.0': dependencies: '@babel/runtime': 7.24.7 - '@wordpress/dom@2.18.0': - dependencies: - '@babel/runtime': 7.24.7 - lodash: 4.17.21 - - '@wordpress/dom@3.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/deprecated': 3.58.0 - '@wordpress/dom@4.2.0': dependencies: '@babel/runtime': 7.24.7 @@ -20236,27 +19805,6 @@ snapshots: - supports-color - utf-8-validate - '@wordpress/element@2.20.3': - dependencies: - '@babel/runtime': 7.24.7 - '@types/react': 16.14.60 - '@types/react-dom': 16.9.24 - '@wordpress/escape-html': 1.12.2 - lodash: 4.17.21 - react: 16.14.0 - react-dom: 16.14.0(react@16.14.0) - - '@wordpress/element@5.35.0': - dependencies: - '@babel/runtime': 7.24.7 - '@types/react': 18.3.1 - '@types/react-dom': 18.3.0 - '@wordpress/escape-html': 2.58.0 - change-case: 4.1.2 - is-plain-object: 5.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - '@wordpress/element@6.2.0': dependencies: '@babel/runtime': 7.24.7 @@ -20268,14 +19816,6 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@wordpress/escape-html@1.12.2': - dependencies: - '@babel/runtime': 7.24.7 - - '@wordpress/escape-html@2.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/escape-html@3.2.0': dependencies: '@babel/runtime': 7.24.7 @@ -20329,45 +19869,14 @@ snapshots: - '@types/react' - supports-color - '@wordpress/hooks@2.12.3': - dependencies: - '@babel/runtime': 7.24.7 - - '@wordpress/hooks@3.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/hooks@4.2.0': dependencies: '@babel/runtime': 7.24.7 - '@wordpress/html-entities@3.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/html-entities@4.2.0': dependencies: '@babel/runtime': 7.24.7 - '@wordpress/i18n@3.20.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/hooks': 2.12.3 - gettext-parser: 1.4.0 - lodash: 4.17.21 - memize: 1.1.0 - sprintf-js: 1.1.3 - tannin: 1.2.0 - - '@wordpress/i18n@4.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/hooks': 3.58.0 - gettext-parser: 1.4.0 - memize: 2.1.0 - sprintf-js: 1.1.3 - tannin: 1.2.0 - '@wordpress/i18n@5.2.0': dependencies: '@babel/runtime': 7.24.7 @@ -20384,13 +19893,6 @@ snapshots: '@wordpress/primitives': 4.2.0(react@18.3.1) react: 18.3.1 - '@wordpress/icons@9.49.0(react@18.3.1)': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/element': 5.35.0 - '@wordpress/primitives': 3.56.0(react@18.3.1) - react: 18.3.1 - '@wordpress/interactivity-router@2.2.0': dependencies: '@wordpress/interactivity': 6.2.0 @@ -20453,14 +19955,6 @@ snapshots: - '@types/react' - supports-color - '@wordpress/is-shallow-equal@3.1.3': - dependencies: - '@babel/runtime': 7.24.7 - - '@wordpress/is-shallow-equal@4.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/is-shallow-equal@5.2.0': dependencies: '@babel/runtime': 7.24.7 @@ -20479,17 +19973,6 @@ snapshots: '@wordpress/keycodes': 4.2.0 react: 18.3.1 - '@wordpress/keycodes@2.19.3': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/i18n': 3.20.0 - lodash: 4.17.21 - - '@wordpress/keycodes@3.58.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/i18n': 4.58.0 - '@wordpress/keycodes@4.2.0': dependencies: '@babel/runtime': 7.24.7 @@ -20646,13 +20129,6 @@ snapshots: dependencies: prettier: wp-prettier@3.0.3 - '@wordpress/primitives@3.56.0(react@18.3.1)': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/element': 5.35.0 - clsx: 2.1.1 - react: 18.3.1 - '@wordpress/primitives@4.2.0(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 @@ -20660,43 +20136,15 @@ snapshots: clsx: 2.1.1 react: 18.3.1 - '@wordpress/priority-queue@1.11.2': - dependencies: - '@babel/runtime': 7.24.7 - - '@wordpress/priority-queue@2.58.0': - dependencies: - '@babel/runtime': 7.24.7 - requestidlecallback: 0.3.0 - '@wordpress/priority-queue@3.2.0': dependencies: '@babel/runtime': 7.24.7 requestidlecallback: 0.3.0 - '@wordpress/private-apis@0.40.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/private-apis@1.2.0': dependencies: '@babel/runtime': 7.24.7 - '@wordpress/react-i18n@3.56.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/element': 5.35.0 - '@wordpress/i18n': 4.58.0 - utility-types: 3.11.0 - - '@wordpress/redux-routine@4.58.0(redux@4.2.1)': - dependencies: - '@babel/runtime': 7.24.7 - is-plain-object: 5.0.0 - is-promise: 4.0.0 - redux: 4.2.1 - rungen: 0.3.2 - '@wordpress/redux-routine@5.2.0(redux@4.2.1)': dependencies: '@babel/runtime': 7.24.7 @@ -20751,20 +20199,6 @@ snapshots: - supports-color - utf-8-validate - '@wordpress/rich-text@6.35.0(react@18.3.1)': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/a11y': 3.58.0 - '@wordpress/compose': 6.35.0(react@18.3.1) - '@wordpress/data': 9.28.0(react@18.3.1) - '@wordpress/deprecated': 3.58.0 - '@wordpress/element': 5.35.0 - '@wordpress/escape-html': 2.58.0 - '@wordpress/i18n': 4.58.0 - '@wordpress/keycodes': 3.58.0 - memize: 2.1.0 - react: 18.3.1 - '@wordpress/rich-text@7.2.0(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 @@ -20859,11 +20293,6 @@ snapshots: dependencies: '@babel/runtime': 7.24.7 - '@wordpress/undo-manager@0.18.0': - dependencies: - '@babel/runtime': 7.24.7 - '@wordpress/is-shallow-equal': 4.58.0 - '@wordpress/undo-manager@1.2.0': dependencies: '@babel/runtime': 7.24.7 @@ -20882,8 +20311,6 @@ snapshots: '@wordpress/element': 6.2.0 react: 18.3.1 - '@wordpress/warning@2.58.0': {} - '@wordpress/warning@3.2.0': {} '@wordpress/widgets@4.2.0(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -21584,8 +21011,6 @@ snapshots: caniuse-lite@1.0.30001640: {} - canvas-confetti@1.9.3: {} - capital-case@1.0.4: dependencies: no-case: 3.0.4 @@ -21729,8 +21154,6 @@ snapshots: cjs-module-lexer@1.3.1: {} - classnames@2.5.1: {} - clean-css@5.3.3: dependencies: source-map: 0.6.1 @@ -23103,8 +22526,6 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - exenv@1.2.2: {} - exit@0.1.2: {} expand-tilde@1.2.2: @@ -23651,11 +23072,6 @@ snapshots: prop-types: 15.8.1 react: 18.3.1 - gridicons@3.4.2(react@18.3.1): - dependencies: - prop-types: 15.8.1 - react: 18.3.1 - gunzip-maybe@1.4.2: dependencies: browserify-zlib: 0.1.4 @@ -23854,24 +23270,6 @@ snapshots: husky@8.0.3: {} - i18n-calypso@7.0.0(@types/react@18.3.1)(react@18.3.1): - dependencies: - '@automattic/interpolate-components': 1.2.1(@types/react@18.3.1)(react@18.3.1) - '@babel/runtime': 7.24.7 - '@tannin/sprintf': 1.2.0 - '@wordpress/compose': 6.35.0(react@18.3.1) - debug: 4.3.4 - events: 3.3.0 - hash.js: 1.1.7 - lodash: 4.17.21 - lru: 3.1.0 - react: 18.3.1 - tannin: 1.2.0 - use-subscription: 1.6.0(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - supports-color - iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -25089,10 +24487,6 @@ snapshots: lru-cache@7.18.3: {} - lru@3.1.0: - dependencies: - inherits: 2.0.4 - lz-string@1.5.0: {} magic-string@0.27.0: @@ -25282,8 +24676,6 @@ snapshots: dependencies: fs-monkey: 1.0.6 - memize@1.1.0: {} - memize@2.1.0: {} memoizerific@1.11.3: @@ -26523,14 +25915,6 @@ snapshots: transitivePeerDependencies: - supports-color - react-dom@16.14.0(react@16.14.0): - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - prop-types: 15.8.1 - react: 16.14.0 - scheduler: 0.19.1 - react-dom@18.2.0(react@18.2.0): dependencies: loose-envify: 1.4.0 @@ -26558,6 +25942,8 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-is: 18.1.0 + react-fast-compare@3.2.2: {} + react-is@16.13.1: {} react-is@17.0.2: {} @@ -26566,21 +25952,18 @@ snapshots: react-is@18.3.1: {} - react-lifecycles-compat@3.0.4: {} - - react-modal@3.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-page-visibility@7.0.0(react@18.3.1): dependencies: - exenv: 1.2.2 prop-types: 15.8.1 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-lifecycles-compat: 3.0.4 - warning: 4.0.3 - react-page-visibility@7.0.0(react@18.3.1): + react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - prop-types: 15.8.1 + '@popperjs/core': 2.11.8 react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-fast-compare: 3.2.2 + warning: 4.0.3 react-redux@7.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: @@ -26648,10 +26031,6 @@ snapshots: use-callback-ref: 1.3.2(react@18.3.1) use-sidecar: 1.1.2(react@18.3.1) - react-resize-aware@3.1.3(react@18.3.1): - dependencies: - react: 18.3.1 - react-router-dom@5.3.4(react@18.3.1): dependencies: '@babel/runtime': 7.24.7 @@ -26748,11 +26127,6 @@ snapshots: prop-types: 15.8.1 react: 18.3.1 - react-slider@2.0.5(react@18.3.1): - dependencies: - prop-types: 15.8.1 - react: 18.3.1 - react-style-singleton@2.2.1(@types/react@18.3.1)(react@18.3.1): dependencies: get-nonce: 1.0.1 @@ -26791,12 +26165,6 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react@16.14.0: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - prop-types: 15.8.1 - react@18.2.0: dependencies: loose-envify: 1.4.0 @@ -27169,11 +26537,6 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.19.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -28199,10 +27562,6 @@ snapshots: react: 18.3.1 tslib: 2.5.0 - use-subscription@1.6.0(react@18.3.1): - dependencies: - react: 18.3.1 - use-sync-external-store@1.2.2(react@18.3.1): dependencies: react: 18.3.1 @@ -28219,8 +27578,6 @@ snapshots: utila@0.4.0: {} - utility-types@3.11.0: {} - utils-merge@1.0.1: {} uuid@8.3.2: {} diff --git a/projects/packages/jetpack-mu-wpcom/package.json b/projects/packages/jetpack-mu-wpcom/package.json index 3a02cc6c09200..cda44779f2b46 100644 --- a/projects/packages/jetpack-mu-wpcom/package.json +++ b/projects/packages/jetpack-mu-wpcom/package.json @@ -33,6 +33,7 @@ "@babel/core": "7.24.7", "@babel/plugin-transform-react-jsx": "7.24.7", "@babel/preset-react": "7.24.7", + "@babel/runtime": "^7.24.5", "@playwright/test": "1.45.1", "@types/node": "^20.4.2", "@types/react": "^18.2.28", @@ -46,12 +47,13 @@ }, "dependencies": { "@automattic/calypso-analytics": "1.1.2", - "@automattic/components": "2.1.1", + "@automattic/calypso-color-schemes": "3.1.3", "@automattic/i18n-utils": "1.2.3", "@automattic/jetpack-base-styles": "workspace:*", "@automattic/jetpack-shared-extension-utils": "workspace:*", "@automattic/typography": "1.0.0", "@automattic/viewport": "1.0.0", + "@popperjs/core": "^2.11.8", "@preact/signals": "^1.2.2", "@sentry/browser": "7.80.1", "@tanstack/react-query": "^5.15.5", @@ -68,9 +70,12 @@ "@wordpress/plugins": "7.2.0", "@wordpress/url": "4.2.0", "clsx": "2.1.1", + "debug": "4.3.4", "preact": "^10.13.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-popper": "^2.3.0", + "redux": "^4.2.1", "wpcom-proxy-request": "^7.0.3" } } diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx new file mode 100644 index 0000000000000..7d913d40d3a7f --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx @@ -0,0 +1,61 @@ +/** + * External dependencies + */ +import * as React from 'react'; +/** + * Internal dependencies + */ +import useFocusHandler from '../hooks/use-focus-handler'; +import useFocusTrap from '../hooks/use-focus-trap'; +import useKeydownHandler from '../hooks/use-keydown-handler'; + +interface Props { + onMinimize: () => void; + onDismiss: ( target: string ) => () => void; + onNextStepProgression: () => void; + onPreviousStepProgression: () => void; + tourContainerRef: React.MutableRefObject< null | HTMLElement >; + isMinimized: boolean; +} + +const KeyboardNavigation: React.FunctionComponent< Props > = ( { + onMinimize, + onDismiss, + onNextStepProgression, + onPreviousStepProgression, + tourContainerRef, + isMinimized, +} ) => { + /** + * Expand Tour Nav + */ + function ExpandedTourNav() { + useKeydownHandler( { + onEscape: onMinimize, + onArrowRight: onNextStepProgression, + onArrowLeft: onPreviousStepProgression, + } ); + useFocusTrap( tourContainerRef ); + + return null; + } + + /** + * Minimize Tour Nav + */ + function MinimizedTourNav() { + useKeydownHandler( { onEscape: onDismiss( 'esc-key-minimized' ) } ); + + return null; + } + + const isTourFocused = useFocusHandler( tourContainerRef ); + + if ( ! isTourFocused ) { + return null; + } + + return isMinimized ? : ; +}; + +export default KeyboardNavigation; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx new file mode 100644 index 0000000000000..e030a37cdda26 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx @@ -0,0 +1,286 @@ +/** + * External Dependencies + */ +import { useViewportMatch } from '@wordpress/compose'; +import { useEffect, useState, useCallback, useMemo, useRef } from '@wordpress/element'; +import clsx from 'clsx'; +import { usePopper } from 'react-popper'; +/** + * Internal Dependencies + */ +import useStepTracking from '../hooks/use-step-tracking'; +import { classParser } from '../utils'; +import { liveResizeModifier } from '../utils/live-resize-modifier'; +import KeyboardNavigation from './keyboard-navigation'; +import TourKitMinimized from './tour-kit-minimized'; +import Overlay from './tour-kit-overlay'; +import Spotlight from './tour-kit-spotlight'; +import TourKitStep from './tour-kit-step'; +import type { Callback, Config } from '../types'; + +const handleCallback = ( currentStepIndex: number, callback?: Callback ) => { + typeof callback === 'function' && callback( currentStepIndex ); +}; + +interface Props { + config: Config; +} + +const TourKitFrame: React.FunctionComponent< Props > = ( { config } ) => { + const [ currentStepIndex, setCurrentStepIndex ] = useState( 0 ); + const [ initialFocusedElement, setInitialFocusedElement ] = useState< HTMLElement | null >( + null + ); + const [ isMinimized, setIsMinimized ] = useState( config.isMinimized ?? false ); + + const [ popperElement, setPopperElement ] = useState< HTMLElement | null >( null ); + const [ tourReady, setTourReady ] = useState( false ); + const tourContainerRef = useRef( null ); + const isMobile = useViewportMatch( 'mobile', '<' ); + const lastStepIndex = config.steps.length - 1; + const referenceElements = config.steps[ currentStepIndex ].referenceElements; + const referenceElementSelector = + referenceElements?.[ isMobile ? 'mobile' : 'desktop' ] || referenceElements?.desktop; + const referenceElement = referenceElementSelector + ? document.querySelector< HTMLElement >( referenceElementSelector ) + : null; + + useEffect( () => { + if ( config.isMinimized ) { + setIsMinimized( true ); + } + }, [ config.isMinimized ] ); + + const showArrowIndicator = useCallback( () => { + if ( config.options?.effects?.arrowIndicator === false ) { + return false; + } + + return !! ( referenceElement && ! isMinimized && tourReady ); + }, [ config.options?.effects?.arrowIndicator, isMinimized, referenceElement, tourReady ] ); + + const showSpotlight = useCallback( () => { + if ( ! config.options?.effects?.spotlight ) { + return false; + } + + return ! isMinimized; + }, [ config.options?.effects?.spotlight, isMinimized ] ); + + const showOverlay = useCallback( () => { + if ( showSpotlight() || ! config.options?.effects?.overlay ) { + return false; + } + + return ! isMinimized; + }, [ config.options?.effects?.overlay, isMinimized, showSpotlight ] ); + + const handleDismiss = useCallback( + ( source: string ) => { + return () => { + config.closeHandler( config.steps, currentStepIndex, source ); + }; + }, + [ config, currentStepIndex ] + ); + + const handleNextStepProgression = useCallback( () => { + let newStepIndex = currentStepIndex; + if ( lastStepIndex > currentStepIndex ) { + newStepIndex = currentStepIndex + 1; + setCurrentStepIndex( newStepIndex ); + } + handleCallback( newStepIndex, config.options?.callbacks?.onNextStep ); + }, [ config.options?.callbacks?.onNextStep, currentStepIndex, lastStepIndex ] ); + + const handlePreviousStepProgression = useCallback( () => { + let newStepIndex = currentStepIndex; + if ( currentStepIndex > 0 ) { + newStepIndex = currentStepIndex - 1; + setCurrentStepIndex( newStepIndex ); + } + handleCallback( newStepIndex, config.options?.callbacks?.onPreviousStep ); + }, [ config.options?.callbacks?.onPreviousStep, currentStepIndex ] ); + + const handleGoToStep = useCallback( + ( stepIndex: number ) => { + setCurrentStepIndex( stepIndex ); + handleCallback( stepIndex, config.options?.callbacks?.onGoToStep ); + }, + [ config.options?.callbacks?.onGoToStep ] + ); + + const handleMinimize = useCallback( () => { + setIsMinimized( true ); + handleCallback( currentStepIndex, config.options?.callbacks?.onMinimize ); + }, [ config.options?.callbacks?.onMinimize, currentStepIndex ] ); + + const handleMaximize = useCallback( () => { + setIsMinimized( false ); + handleCallback( currentStepIndex, config.options?.callbacks?.onMaximize ); + }, [ config.options?.callbacks?.onMaximize, currentStepIndex ] ); + + const { + styles: popperStyles, + attributes: popperAttributes, + update: popperUpdate, + } = usePopper( referenceElement, popperElement, { + strategy: 'fixed', + placement: config?.placement ?? 'bottom', + modifiers: [ + { + name: 'preventOverflow', + options: { + rootBoundary: 'document', + padding: 16, // same as the left/margin of the tour frame + }, + }, + { + name: 'arrow', + options: { + padding: 12, + }, + }, + { + name: 'offset', + options: { + offset: [ 0, showArrowIndicator() ? 12 : 10 ], + }, + }, + { + name: 'flip', + options: { + fallbackPlacements: [ 'top', 'left', 'right' ], + }, + }, + useMemo( + () => liveResizeModifier( config.options?.effects?.liveResize ), + [ config.options?.effects?.liveResize ] + ), + ...( config.options?.popperModifiers || [] ), + ], + } ); + + const stepRepositionProps = + ! isMinimized && referenceElement && tourReady + ? { + style: popperStyles?.popper, + ...popperAttributes?.popper, + } + : null; + + const arrowPositionProps = + ! isMinimized && referenceElement && tourReady + ? { + style: popperStyles?.arrow, + ...popperAttributes?.arrow, + } + : null; + + /* + * Focus first interactive element when step renders. + */ + useEffect( () => { + setTimeout( () => initialFocusedElement?.focus() ); + }, [ initialFocusedElement ] ); + + /* + * Fixes issue with Popper misplacing the instance on mount + * See: https://stackoverflow.com/questions/65585859/react-popper-incorrect-position-on-mount + */ + useEffect( () => { + // If no reference element to position step near + if ( ! referenceElement ) { + setTourReady( true ); + return; + } + + setTourReady( false ); + + if ( popperUpdate ) { + popperUpdate() + .then( () => setTourReady( true ) ) + .catch( () => setTourReady( true ) ); + } + }, [ popperUpdate, referenceElement ] ); + + useEffect( () => { + if ( referenceElement && config.options?.effects?.autoScroll ) { + referenceElement.scrollIntoView( config.options.effects.autoScroll ); + } + }, [ config.options?.effects?.autoScroll, referenceElement ] ); + + const classes = clsx( + 'tour-kit-frame', + isMobile ? 'is-mobile' : 'is-desktop', + { 'is-visible': tourReady }, + classParser( config.options?.classNames ) + ); + + useStepTracking( currentStepIndex, config.options?.callbacks?.onStepViewOnce ); + + useEffect( () => { + if ( config.options?.callbacks?.onStepView ) { + handleCallback( currentStepIndex, config.options?.callbacks?.onStepView ); + } + }, [ config.options?.callbacks?.onStepView, currentStepIndex ] ); + + return ( + <> + +
+ { showOverlay() && } + { showSpotlight() && ( + + ) } +
) } + > + { showArrowIndicator() && ( +
) } + /> + ) } + { ! isMinimized ? ( + + ) : ( + + ) } +
+
+ + ); +}; + +export default TourKitFrame; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx new file mode 100644 index 0000000000000..6831fbf8f6051 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx @@ -0,0 +1,30 @@ +/** + * Internal Dependencies + */ +import { MinimizedTourRendererProps } from '../types'; +import type { Config } from '../types'; + +interface Props extends MinimizedTourRendererProps { + config: Config; +} + +const TourKitMinimized: React.FunctionComponent< Props > = ( { + config, + steps, + currentStepIndex, + onMaximize, + onDismiss, +} ) => { + return ( +
+ +
+ ); +}; + +export default TourKitMinimized; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx new file mode 100644 index 0000000000000..3c440bcdc2de7 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx @@ -0,0 +1,20 @@ +/** + * External Dependencies + */ +import clsx from 'clsx'; + +interface Props { + visible: boolean; +} + +const TourKitOverlay: React.FunctionComponent< Props > = ( { visible } ) => { + return ( +
+ ); +}; + +export default TourKitOverlay; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx new file mode 100644 index 0000000000000..61b7e94bca203 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx @@ -0,0 +1,38 @@ +import { SPOTLIT_ELEMENT_CLASS } from './tour-kit-spotlight'; + +export interface SpotlightInteractivityConfiguration { + /** If true, the user will be allowed to interact with the spotlit element. Defaults to false. */ + enabled?: boolean; + /** + * This element is the root element within which all children will have + * pointer-events disabled during the tour. Defaults to '#wpwrap' + */ + rootElementSelector?: string; +} + +export const SpotlightInteractivity: React.VFC< SpotlightInteractivityConfiguration > = ( { + enabled = false, + rootElementSelector = '#wpwrap', +}: SpotlightInteractivityConfiguration ) => { + if ( ! enabled ) { + return null; + } + return ( + + ); +}; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx new file mode 100644 index 0000000000000..81e9ed80ff131 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx @@ -0,0 +1,122 @@ +import { useMemo, useState, useEffect } from '@wordpress/element'; +import clsx from 'clsx'; +import { usePopper } from 'react-popper'; +import { LiveResizeConfiguration, liveResizeModifier } from '../utils/live-resize-modifier'; +import Overlay from './tour-kit-overlay'; +import { + SpotlightInteractivity, + SpotlightInteractivityConfiguration, +} from './tour-kit-spotlight-interactivity'; +import type { Rect, Placement } from '@popperjs/core'; + +export const SPOTLIT_ELEMENT_CLASS = 'wp-tour-kit-spotlit'; +interface Props { + referenceElement: HTMLElement | null; + styles?: React.CSSProperties; + interactivity?: SpotlightInteractivityConfiguration; + liveResize?: LiveResizeConfiguration; +} + +const TourKitSpotlight: React.FunctionComponent< Props > = ( { + referenceElement, + styles, + interactivity, + liveResize, +} ) => { + const [ popperElement, sePopperElement ] = useState< HTMLElement | null >( null ); + const referenceRect = referenceElement?.getBoundingClientRect(); + + const modifiers = [ + { + name: 'flip', + enabled: false, + }, + { + name: 'preventOverflow', + options: { + mainAxis: false, // true by default + }, + }, + useMemo( + () => ( { + name: 'offset', + options: { + offset: ( { + placement, + reference, + popper, + }: { + placement: Placement; + reference: Rect; + popper: Rect; + } ): [ number, number ] => { + if ( placement === 'bottom' ) { + return [ 0, -( reference.height + ( popper.height - reference.height ) / 2 ) ]; + } + return [ 0, 0 ]; + }, + }, + } ), + [] + ), + // useMemo because https://popper.js.org/react-popper/v2/faq/#why-i-get-render-loop-whenever-i-put-a-function-inside-the-popper-configuration + useMemo( () => { + return liveResizeModifier( liveResize ); + }, [ liveResize ] ), + ]; + + const { styles: popperStyles, attributes: popperAttributes } = usePopper( + referenceElement, + popperElement, + { + strategy: 'fixed', + placement: 'bottom', + modifiers, + } + ); + + const clipDimensions = referenceRect + ? { + width: `${ referenceRect.width }px`, + height: `${ referenceRect.height }px`, + } + : null; + + const clipRepositionProps = referenceElement + ? { + style: { + ...( clipDimensions && clipDimensions ), + ...popperStyles?.popper, + ...( styles && styles ), + }, + ...popperAttributes?.popper, + } + : null; + + /** + * Add a .wp-spotlit class to the referenced element so that we can + * apply CSS styles to it, for whatever purposes such as interactivity + */ + useEffect( () => { + referenceElement?.classList.add( SPOTLIT_ELEMENT_CLASS ); + return () => { + referenceElement?.classList.remove( SPOTLIT_ELEMENT_CLASS ); + }; + }, [ referenceElement ] ); + + return ( + <> + + +
) } + /> + + ); +}; + +export default TourKitSpotlight; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx new file mode 100644 index 0000000000000..ded21b229ed03 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx @@ -0,0 +1,52 @@ +/** + * External Dependencies + */ +import { useViewportMatch } from '@wordpress/compose'; +import clsx from 'clsx'; +/** + * Internal Dependencies + */ +import { classParser } from '../utils'; +import type { Config, TourStepRendererProps } from '../types'; + +interface Props extends TourStepRendererProps { + config: Config; +} + +const TourKitStep: React.FunctionComponent< Props > = ( { + config, + steps, + currentStepIndex, + onMinimize, + onDismiss, + onNextStep, + onPreviousStep, + setInitialFocusedElement, + onGoToStep, +} ) => { + const isMobile = useViewportMatch( 'mobile', '<' ); + const classes = clsx( + 'tour-kit-step', + `is-step-${ currentStepIndex }`, + classParser( + config.steps[ currentStepIndex ].options?.classNames?.[ isMobile ? 'mobile' : 'desktop' ] + ) + ); + + return ( +
+ +
+ ); +}; + +export default TourKitStep; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx new file mode 100644 index 0000000000000..d8b19a648d14c --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx @@ -0,0 +1,40 @@ +import { createPortal, useEffect, useRef } from '@wordpress/element'; +import React from 'react'; +import TourKitContextProvider from '../contexts/tour-kit-context'; +import ErrorBoundary from '../error-boundary'; +import TourKitFrame from './tour-kit-frame'; +import type { Config } from '../types'; + +import '../styles.scss'; + +interface Props { + config: Config; + __temp__className?: string; +} + +const TourKit: React.FunctionComponent< Props > = ( { config, __temp__className } ) => { + const portalParent = useRef( document.createElement( 'div' ) ).current; + + useEffect( () => { + const classes = [ 'tour-kit', ...( __temp__className ? [ __temp__className ] : [] ) ]; + + portalParent.classList.add( ...classes ); + + const portalParentElement = config.options?.portalParentElement || document.body; + portalParentElement.appendChild( portalParent ); + + return () => { + portalParentElement.removeChild( portalParent ); + }; + }, [ __temp__className, portalParent, config.options?.portalParentElement ] ); + + return ( + + +
{ createPortal( , portalParent ) }
+
+
+ ); +}; + +export default TourKit; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts new file mode 100644 index 0000000000000..b8522601535a0 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts @@ -0,0 +1,3 @@ +// Copied from https://github.com/Automattic/wp-calypso/blob/ce1a376af4bcc8987855ced4c961efacda6e1d32/packages/onboarding/src/utils/flows.ts#L32-L33 +export const START_WRITING_FLOW = 'start-writing'; +export const DESIGN_FIRST_FLOW = 'design-first'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx new file mode 100644 index 0000000000000..94bc6c49e7e13 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx @@ -0,0 +1,68 @@ +import apiFetch from '@wordpress/api-fetch'; +import { useState, useEffect } from '@wordpress/element'; +import * as React from 'react'; +import { useContext } from 'react'; + +type HasSeenSCModalResult = { + has_seen_seller_celebration_modal: boolean; +}; + +type HasSeenSellerCelebrationModalContextType = { + hasSeenSellerCelebrationModal: boolean; + updateHasSeenSellerCelebrationModal: ( value: boolean ) => void; +}; + +const HasSeenSCModalContext = React.createContext< HasSeenSellerCelebrationModalContextType >( { + hasSeenSellerCelebrationModal: false, + // eslint-disable-next-line @typescript-eslint/no-empty-function + updateHasSeenSellerCelebrationModal: () => {}, +} ); + +export const useHasSeenSellerCelebrationModal = () => { + return useContext( HasSeenSCModalContext ); +}; + +export const HasSeenSellerCelebrationModalProvider: React.FC< { children: JSX.Element } > = + function ( { children } ) { + const [ hasSeenSellerCelebrationModal, setHasSeenSellerCelebrationModal ] = useState( false ); + + /** + * Fetch the value that whether the video celebration modal has been seen. + */ + function fetchHasSeenSellerCelebrationModal() { + apiFetch< HasSeenSCModalResult >( { + path: '/wpcom/v2/block-editor/has-seen-seller-celebration-modal', + } ) + .then( ( result: HasSeenSCModalResult ) => + setHasSeenSellerCelebrationModal( result.has_seen_seller_celebration_modal ) + ) + .catch( () => setHasSeenSellerCelebrationModal( false ) ); + } + + /** + * Update the value that whether the video celebration modal has been seen. + * + * @param value - The value to update. + */ + function updateHasSeenSellerCelebrationModal( value: boolean ) { + apiFetch( { + method: 'PUT', + path: '/wpcom/v2/block-editor/has-seen-seller-celebration-modal', + data: { has_seen_seller_celebration_modal: value }, + } ).finally( () => { + setHasSeenSellerCelebrationModal( true ); + } ); + } + + useEffect( () => { + fetchHasSeenSellerCelebrationModal(); + }, [] ); + + return ( + + { children } + + ); + }; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx new file mode 100644 index 0000000000000..18b9345434ec6 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx @@ -0,0 +1,68 @@ +import apiFetch from '@wordpress/api-fetch'; +import { useState, useEffect } from '@wordpress/element'; +import * as React from 'react'; +import { useContext } from 'react'; + +type HasSeenVCModalResult = { + has_seen_video_celebration_modal: boolean; +}; + +type HasSeenVideoCelebrationModalContextType = { + hasSeenVideoCelebrationModal: boolean; + updateHasSeenVideoCelebrationModal: ( value: boolean ) => void; +}; + +const HasSeenVCModalContext = React.createContext< HasSeenVideoCelebrationModalContextType >( { + hasSeenVideoCelebrationModal: false, + // eslint-disable-next-line @typescript-eslint/no-empty-function + updateHasSeenVideoCelebrationModal: () => {}, +} ); + +export const useHasSeenVideoCelebrationModal = () => { + return useContext( HasSeenVCModalContext ); +}; + +export const HasSeenVideoCelebrationModalProvider: React.FC< { children: JSX.Element } > = + function ( { children } ) { + const [ hasSeenVideoCelebrationModal, setHasSeenVideoCelebrationModal ] = useState( false ); + + useEffect( () => { + fetchHasSeenVideoCelebrationModal(); + }, [] ); + + /** + * Fetch the value that whether the video celebration modal has been seen. + */ + function fetchHasSeenVideoCelebrationModal() { + apiFetch< HasSeenVCModalResult >( { + path: '/wpcom/v2/block-editor/has-seen-video-celebration-modal', + } ) + .then( ( result: HasSeenVCModalResult ) => + setHasSeenVideoCelebrationModal( result.has_seen_video_celebration_modal ) + ) + .catch( () => setHasSeenVideoCelebrationModal( false ) ); + } + + /** + * Update the value that whether the video celebration modal has been seen. + * + * @param value - The value to update. + */ + function updateHasSeenVideoCelebrationModal( value: boolean ) { + apiFetch( { + method: 'PUT', + path: '/wpcom/v2/block-editor/has-seen-video-celebration-modal', + data: { has_seen_video_celebration_modal: value }, + } ).finally( () => { + setHasSeenVideoCelebrationModal( value ); + } ); + } + + return ( + + { children } + + ); + }; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx new file mode 100644 index 0000000000000..d9b5b30e8ee0f --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx @@ -0,0 +1,31 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import * as React from 'react'; +import { useContext } from 'react'; + +const ShouldShowFPPModalContext = React.createContext( false ); + +export const useShouldShowFirstPostPublishedModal = () => { + return useContext( ShouldShowFPPModalContext ); +}; + +export const ShouldShowFirstPostPublishedModalProvider = ( { children } ) => { + const value = useSelect( + select => select( 'automattic/wpcom-welcome-guide' ).getShouldShowFirstPostPublishedModal(), + [] + ); + + const { fetchShouldShowFirstPostPublishedModal } = useDispatch( + 'automattic/wpcom-welcome-guide' + ); + + useEffect( () => { + fetchShouldShowFirstPostPublishedModal(); + }, [ fetchShouldShowFirstPostPublishedModal ] ); + + return ( + + { children } + + ); +}; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx new file mode 100644 index 0000000000000..f9524abecdbd7 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx @@ -0,0 +1,18 @@ +import { createContext, useContext } from '@wordpress/element'; +import type { Config } from '../types'; + +interface TourKitContext { + config: Config; +} + +const TourKitContext = createContext< TourKitContext >( {} as TourKitContext ); + +const TourKitContextProvider: React.FunctionComponent< + TourKitContext & { children?: React.ReactNode } +> = ( { config, children } ) => { + return { children }; +}; + +export const useTourKitContext = (): TourKitContext => useContext( TourKitContext ); + +export default TourKitContextProvider; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx new file mode 100644 index 0000000000000..250ce916b1496 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx @@ -0,0 +1,33 @@ +import React, { ErrorInfo } from 'react'; + +type State = { + hasError: boolean; +}; + +class ErrorBoundary extends React.Component< { children: React.ReactNode }, State > { + state = { + hasError: false, + }; + + static getDerivedStateFromError() { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + componentDidCatch( error: Error, errorInfo: ErrorInfo ) { + // You can also log the error to an error reporting service + // eslint-disable-next-line no-console + console.error( error, errorInfo ); + } + + render() { + if ( this.state.hasError ) { + // You can render any custom fallback UI + return

Something went wrong.

; + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts new file mode 100644 index 0000000000000..7c2fc8e6c2b2b --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts @@ -0,0 +1,9 @@ +export { default as useFocusHandle } from './use-focus-handler'; +export { default as useFocusTrap } from './use-focus-trap'; +export { default as useHasSelectedPaymentBlockOnce } from './use-has-selected-payment-block-once'; +export { default as useKeydownHandler } from './use-keydown-handler'; +export { default as useShouldShowSellerCelebrationModal } from './use-should-show-seller-celebration-modal'; +export { default as useShouldShowVideoCelebrationModal } from './use-should-show-video-celebration-modal'; +export { default as useSiteIntent } from './use-site-intent'; +export { default as useSitePlan } from './use-site-plan'; +export { default as useStepTracking } from './use-step-tracking'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts new file mode 100644 index 0000000000000..7e54ac45673dc --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts @@ -0,0 +1,60 @@ +/** + * External Dependencies + */ +import { useEffect, useCallback, useState } from '@wordpress/element'; + +/** + * A hook that returns true/false if ref node receives focus by either tabbing or clicking into any of its children. + * @param ref - React.MutableRefObject< null | HTMLElement > + */ +const useFocusHandler = ( ref: React.MutableRefObject< null | HTMLElement > ): boolean => { + const [ hasFocus, setHasFocus ] = useState( false ); + + const handleFocus = useCallback( () => { + if ( document.hasFocus() && ref.current?.contains( document.activeElement ) ) { + setHasFocus( true ); + } else { + setHasFocus( false ); + } + }, [ ref ] ); + + const handleMousedown = useCallback( + ( event: MouseEvent ) => { + if ( ref.current?.contains( event.target as Node ) ) { + setHasFocus( true ); + } else { + setHasFocus( false ); + } + }, + [ ref ] + ); + + const handleKeyup = useCallback( + ( event: KeyboardEvent ) => { + if ( event.key === 'Tab' ) { + if ( ref.current?.contains( event.target as Node ) ) { + setHasFocus( true ); + } else { + setHasFocus( false ); + } + } + }, + [ ref ] + ); + + useEffect( () => { + document.addEventListener( 'focusin', handleFocus ); + document.addEventListener( 'mousedown', handleMousedown ); + document.addEventListener( 'keyup', handleKeyup ); + + return () => { + document.removeEventListener( 'focusin', handleFocus ); + document.removeEventListener( 'mousedown', handleMousedown ); + document.removeEventListener( 'keyup', handleKeyup ); + }; + }, [ ref, handleFocus, handleKeyup, handleMousedown ] ); + + return hasFocus; +}; + +export default useFocusHandler; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts new file mode 100644 index 0000000000000..b8e0bc7b313a9 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts @@ -0,0 +1,56 @@ +/** + * External Dependencies + */ +import { focus } from '@wordpress/dom'; +import { useEffect, useCallback, useState } from '@wordpress/element'; +/** + * A hook that constraints tabbing/focus on focuable elements in the given element ref. + * @param ref - React.MutableRefObject< null | HTMLElement > + */ +const useFocusTrap = ( ref: React.MutableRefObject< null | HTMLElement > ): void => { + const [ firstFocusableElement, setFirstFocusableElement ] = useState< HTMLElement | undefined >(); + const [ lastFocusableElement, setLastFocusableElement ] = useState< HTMLElement | undefined >(); + + const handleTrapFocus = useCallback( + ( event: KeyboardEvent ) => { + let handled = false; + + if ( event.key === 'Tab' ) { + if ( event.shiftKey ) { + // Shift + Tab + if ( document.activeElement === firstFocusableElement ) { + lastFocusableElement?.focus(); + handled = true; + } + } else if ( document.activeElement === lastFocusableElement ) { + // Tab + firstFocusableElement?.focus(); + handled = true; + } + } + + if ( handled ) { + event.preventDefault(); + event.stopPropagation(); + } + }, + [ firstFocusableElement, lastFocusableElement ] + ); + + useEffect( () => { + const focusableElements = ref.current ? focus.focusable.find( ref.current as HTMLElement ) : []; + + if ( focusableElements && focusableElements.length ) { + setFirstFocusableElement( focusableElements[ 0 ] as HTMLElement ); + setLastFocusableElement( focusableElements[ focusableElements.length - 1 ] as HTMLElement ); + } + + document.addEventListener( 'keydown', handleTrapFocus ); + + return () => { + document.removeEventListener( 'keydown', handleTrapFocus ); + }; + }, [ ref, handleTrapFocus ] ); +}; + +export default useFocusTrap; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js new file mode 100644 index 0000000000000..f8060b9a2d232 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js @@ -0,0 +1,62 @@ +import { useSelect } from '@wordpress/data'; +import { useState, useEffect } from '@wordpress/element'; + +/** + * Watch the user's block selection and keep a note if they ever select a payments + * block. + * A payments block is any block with 'payments' in the name, like jetpack/simple-payments + * or jetpack/recurring-payments. + * Selecting a block whose direct parent has 'payments' in the name also counts. + * This is to account for clicking inside the button in a payments block, for example. + * @returns {boolean} Has the user selected a payments block (or a direct descendant) at least once? + */ +const useHasSelectedPaymentBlockOnce = () => { + const [ hasSelectedPaymentsOnce, setHasSelectedPaymentsOnce ] = useState( false ); + + // Get the name of the currently selected block + const selectedBlockName = useSelect( select => { + // Special case: We know we're returning true, so we don't need to find block names. + if ( hasSelectedPaymentsOnce ) { + return ''; + } + + const selectedBlock = select( 'core/block-editor' ).getSelectedBlock(); + return selectedBlock?.name ?? ''; + } ); + + // Get the name of the currently selected block's direct parent, if one exists + const parentSelectedBlockName = useSelect( select => { + // Special case: We know we're returning true, so we don't need to find block names. + if ( hasSelectedPaymentsOnce ) { + return ''; + } + + const selectedBlock = select( 'core/block-editor' ).getSelectedBlock(); + if ( selectedBlock?.clientId ) { + const parentIds = select( 'core/block-editor' ).getBlockParents( selectedBlock?.clientId ); + if ( parentIds && parentIds.length ) { + const parent = select( 'core/block-editor' ).getBlock( parentIds[ parentIds.length - 1 ] ); + return parent?.name ?? ''; + } + } + return ''; + } ); + + // On selection change, set hasSelectedPaymentsOnce=true if block name or parent's block name contains 'payments' + useEffect( () => { + if ( + ! hasSelectedPaymentsOnce && + ( selectedBlockName.includes( 'payments' ) || parentSelectedBlockName.includes( 'payments' ) ) + ) { + setHasSelectedPaymentsOnce( true ); + } + }, [ + selectedBlockName, + parentSelectedBlockName, + hasSelectedPaymentsOnce, + setHasSelectedPaymentsOnce, + ] ); + + return hasSelectedPaymentsOnce; +}; +export default useHasSelectedPaymentBlockOnce; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts new file mode 100644 index 0000000000000..6ea129fe08f2e --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts @@ -0,0 +1,52 @@ +/* eslint-disable jsdoc/require-param */ +/** + * External Dependencies + */ +import { useEffect, useCallback } from '@wordpress/element'; + +interface Props { + onEscape?: () => void; + onArrowRight?: () => void; + onArrowLeft?: () => void; +} + +/** + * A hook the applies the respective callbacks in response to keydown events. + */ +const useKeydownHandler = ( { onEscape, onArrowRight, onArrowLeft }: Props ): void => { + const handleKeydown = useCallback( + ( event: KeyboardEvent ) => { + let handled = false; + + switch ( event.key ) { + case 'Escape': + onEscape && ( onEscape(), ( handled = true ) ); + break; + case 'ArrowRight': + onArrowRight && ( onArrowRight(), ( handled = true ) ); + break; + case 'ArrowLeft': + onArrowLeft && ( onArrowLeft(), ( handled = true ) ); + break; + default: + break; + } + + if ( handled ) { + event.preventDefault(); + event.stopPropagation(); + } + }, + [ onEscape, onArrowRight, onArrowLeft ] + ); + + useEffect( () => { + document.addEventListener( 'keydown', handleKeydown ); + + return () => { + document.removeEventListener( 'keydown', handleKeydown ); + }; + }, [ handleKeydown ] ); +}; + +export default useKeydownHandler; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js new file mode 100644 index 0000000000000..80f37b3b530cf --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js @@ -0,0 +1,67 @@ +import { useSelect } from '@wordpress/data'; +import { useState, useEffect } from '@wordpress/element'; +import { useHasSeenSellerCelebrationModal } from '../contexts/has-seen-seller-celebration-modal-context'; +import useHasSelectedPaymentBlockOnce from './use-has-selected-payment-block-once'; +import useSiteIntent from './use-site-intent'; + +const useShouldShowSellerCelebrationModal = () => { + const [ shouldShowSellerCelebrationModal, setShouldShowSellerCelebrationModal ] = + useState( false ); + + const { siteIntent: intent } = useSiteIntent(); + const hasSelectedPaymentsOnce = useHasSelectedPaymentBlockOnce(); + + const { hasSeenSellerCelebrationModal } = useHasSeenSellerCelebrationModal(); + + const hasPaymentsBlock = useSelect( select => { + const isSiteEditor = !! select( 'core/edit-site' ); + + if ( isSiteEditor ) { + const page = select( 'core/edit-site' ).getPage(); + const pageId = parseInt( page?.context?.postId ); + const pageEntity = select( 'core' ).getEntityRecord( 'postType', 'page', pageId ); + + let paymentsBlock = false; + // Only check for payment blocks if we haven't seen the celebration modal text yet + if ( ! hasSeenSellerCelebrationModal ) { + const didCountRecurringPayments = + select( 'core/block-editor' ).getGlobalBlockCount( 'jetpack/recurring-payments' ) > 0; + const didCountSimplePayments = + select( 'core/block-editor' ).getGlobalBlockCount( 'jetpack/simple-payments' ) > 0; + paymentsBlock = + ( pageEntity?.content?.raw?.includes( '' ) || + pageEntity?.content?.raw?.includes( '' ) || + didCountRecurringPayments || + didCountSimplePayments ) ?? + false; + } + + return paymentsBlock; + } + + let paymentBlockCount = 0; + // Only check for payment blocks if we haven't seen the celebration modal yet + if ( ! hasSeenSellerCelebrationModal ) { + paymentBlockCount += select( 'core/block-editor' ).getGlobalBlockCount( + 'jetpack/recurring-payments' + ); + paymentBlockCount += + select( 'core/block-editor' ).getGlobalBlockCount( 'jetpack/simple-payments' ); + } + + return paymentBlockCount > 0; + } ); + + useEffect( () => { + if ( + intent === 'sell' && + hasPaymentsBlock && + hasSelectedPaymentsOnce && + ! hasSeenSellerCelebrationModal + ) { + setShouldShowSellerCelebrationModal( true ); + } + }, [ intent, hasPaymentsBlock, hasSelectedPaymentsOnce, hasSeenSellerCelebrationModal ] ); + return shouldShowSellerCelebrationModal; +}; +export default useShouldShowSellerCelebrationModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts new file mode 100644 index 0000000000000..ebbc5ce2ce1d2 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts @@ -0,0 +1,51 @@ +import { useState, useEffect } from '@wordpress/element'; +import request from 'wpcom-proxy-request'; +import { useHasSeenVideoCelebrationModal } from '../contexts/has-seen-video-celebration-modal-context'; +import useSiteIntent from './use-site-intent'; + +declare global { + interface Window { + _currentSiteId: number; + _currentSiteType: string; + } +} + +interface Site { + options?: { + launchpad_checklist_tasks_statuses?: { + video_uploaded: boolean; + }; + }; +} +const useShouldShowVideoCelebrationModal = ( isEditorSaving: boolean ) => { + const { siteIntent: intent } = useSiteIntent(); + + const [ shouldShowVideoCelebrationModal, setShouldShowVideoCelebrationModal ] = useState( false ); + const { hasSeenVideoCelebrationModal } = useHasSeenVideoCelebrationModal(); + + useEffect( () => { + const maybeRenderVideoCelebrationModal = async () => { + // Get latest site options since the video may have just been uploaded. + const siteObj = ( await request( { + path: `/sites/${ window._currentSiteId }?http_envelope=1`, + apiVersion: '1.1', + } ) ) as Site; + + if ( siteObj?.options?.launchpad_checklist_tasks_statuses?.video_uploaded ) { + setShouldShowVideoCelebrationModal( true ); + } + }; + + if ( 'videopress' === intent && ! hasSeenVideoCelebrationModal ) { + maybeRenderVideoCelebrationModal(); + } else if ( hasSeenVideoCelebrationModal ) { + setShouldShowVideoCelebrationModal( false ); + } + }, [ + isEditorSaving, // included so that we check whether the video has been uploaded on save. + intent, + hasSeenVideoCelebrationModal, + ] ); + return shouldShowVideoCelebrationModal; +}; +export default useShouldShowVideoCelebrationModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js new file mode 100644 index 0000000000000..496ee2bc23bbd --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js @@ -0,0 +1,14 @@ +// FIXME: We can use `useSiteIntent` from `@automattic/data-stores` and remove this. +// https://github.com/Automattic/wp-calypso/pull/73565#discussion_r1113839120 +const useSiteIntent = () => { + // We can skip the request altogether since this information is already added to the window in + // https://github.com/Automattic/jetpack/blob/e135711f9a130946dae1bca6c9c0967350331067/projects/plugins/jetpack/extensions/plugins/launchpad-save-modal/launchpad-save-modal.php#LL31C8-L31C34 + // We could update this to use the launchpad endpoint in jetpack-mu-wpcom, but that may require + // permissions changes as it requires 'manage_options' to read + // https://github.com/Automattic/jetpack/blob/e135711f9a130946dae1bca6c9c0967350331067/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launchpad.php#L121. + return { + siteIntent: window.Jetpack_LaunchpadSaveModal?.siteIntentOption, + siteIntentFetched: true, + }; +}; +export default useSiteIntent; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js new file mode 100644 index 0000000000000..d3c64400da453 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js @@ -0,0 +1,24 @@ +import { useState, useEffect } from '@wordpress/element'; +import { useCallback } from 'react'; +import request from 'wpcom-proxy-request'; + +const useSitePlan = siteIdOrSlug => { + const [ sitePlan, setSitePlan ] = useState( {} ); + + const fetchSite = useCallback( async () => { + const siteObj = await request( { + path: `/sites/${ siteIdOrSlug }?http_envelope=1`, + apiVersion: '1.1', + } ); + if ( siteObj?.plan ) { + setSitePlan( siteObj.plan ); + } + }, [ siteIdOrSlug ] ); + + useEffect( () => { + fetchSite(); + }, [ fetchSite ] ); + + return sitePlan; +}; +export default useSitePlan; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts new file mode 100644 index 0000000000000..a1e231ace668c --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts @@ -0,0 +1,26 @@ +/** + * External Dependencies + */ +import { useState, useEffect } from '@wordpress/element'; +/** + * Internal Dependencies + */ +import { Callback } from '../types'; + +const useStepTracking = ( + currentStepIndex: number, + onStepViewOnce: Callback | undefined +): void => { + const [ stepsViewed, setStepsViewed ] = useState< number[] >( [] ); + + useEffect( () => { + if ( stepsViewed.includes( currentStepIndex ) ) { + return; + } + + setStepsViewed( prev => [ ...prev, currentStepIndex ] ); + onStepViewOnce?.( currentStepIndex ); + }, [ currentStepIndex, onStepViewOnce, stepsViewed ] ); +}; + +export default useStepTracking; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts new file mode 100644 index 0000000000000..44cc9252724ed --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts @@ -0,0 +1,6 @@ +export { default } from './components/tour-kit'; +export * from './constants'; +export { default as WpcomTourKit, usePrefetchTourAssets } from './variants/wpcom'; +export { useTourKitContext } from './contexts/tour-kit-context'; +export * from './types'; +export * from './hooks'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss new file mode 100644 index 0000000000000..ef38a0c05b1e1 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss @@ -0,0 +1,91 @@ +.tour-kit-frame { + visibility: hidden; + + &.is-visible { + visibility: visible; + } +} + +.tour-kit-frame__container { + border-radius: 2px; + bottom: 44px; + display: inline; + left: 16px; + position: fixed; + z-index: 9999; + // Avoid the text cursor when the text is not selectable + cursor: default; + box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.25); + background: var(--studio-white); +} + +.tour-kit-overlay { + position: fixed; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + background: rgba(0, 0, 0); + opacity: 0; + + &.is-visible { + opacity: 0.5; + } +} + +.tour-kit-spotlight { + &.is-visible { + position: fixed; + overflow: hidden; + // box-shadow: 0 0 0 9999px rgba(0, 0, 255, 0.2); + outline: 99999px solid rgba(0, 0, 0, 0.5); + z-index: 1; + } +} + +.tour-kit-frame__arrow { + visibility: hidden; +} + +.tour-kit-frame__arrow, +.tour-kit-frame__arrow::before { + position: absolute; + width: 12px; + height: 12px; + background: inherit; + z-index: -1; +} + +.tour-kit-frame__arrow::before { + visibility: visible; + content: ""; + transform: rotate(45deg); +} + +.tour-kit-frame__container[data-popper-placement^="top"] > .tour-kit-frame__arrow { + bottom: -6px; + &::before { + box-shadow: 1px 1px 2px -1px rgb(0 0 0 / 25%); + } +} + +.tour-kit-frame__container[data-popper-placement^="bottom"] > .tour-kit-frame__arrow { + top: -6px; + &::before { + box-shadow: -1px -1px 2px -1px rgb(0 0 0 / 25%); + } +} + +.tour-kit-frame__container[data-popper-placement^="left"] > .tour-kit-frame__arrow { + right: -6px; + &::before { + box-shadow: 1px -1px 2px -1px rgb(0 0 0 / 25%); + } +} + +.tour-kit-frame__container[data-popper-placement^="right"] > .tour-kit-frame__arrow { + left: -6px; + &::before { + box-shadow: -1px 1px 2px -1px rgb(0 0 0 / 25%); + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts new file mode 100644 index 0000000000000..3f4ec5c080a13 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts @@ -0,0 +1,164 @@ +import * as PopperJS from '@popperjs/core'; +import React from 'react'; +import { SpotlightInteractivityConfiguration } from './components/tour-kit-spotlight-interactivity'; +import { LiveResizeConfiguration } from './utils/live-resize-modifier'; +import type { Modifier } from 'react-popper'; + +export interface Step { + slug?: string; + referenceElements?: { + desktop?: string; + mobile?: string; + }; + meta: { + [ key: string ]: unknown; + // | React.FunctionComponent< Record< string, unknown > > + // | HTMLElement + // | string + // | ... + }; + options?: { + classNames?: { + /** + * `desktop` classes are applied when min-width is larger or equal to 480px. + */ + desktop?: string | string[]; + /** + * `mobile` classes are applied when max-width is smaller than 480px. + */ + mobile?: string | string[]; + }; + }; +} + +export interface TourStepRendererProps { + steps: Step[]; + currentStepIndex: number; + onDismiss: ( source: string ) => () => void; + onNextStep: () => void; + onPreviousStep: () => void; + onMinimize: () => void; + setInitialFocusedElement: React.Dispatch< React.SetStateAction< HTMLElement | null > >; + onGoToStep: ( stepIndex: number ) => void; +} + +export interface MinimizedTourRendererProps { + steps: Step[]; + currentStepIndex: number; + onMaximize: () => void; + onDismiss: ( source: string ) => () => void; +} + +export type TourStepRenderer = React.FunctionComponent< TourStepRendererProps >; +export type MinimizedTourRenderer = React.FunctionComponent< MinimizedTourRendererProps >; +export type Callback = ( currentStepIndex: number ) => void; +export type CloseHandler = ( steps: Step[], currentStepIndex: number, source: string ) => void; +export type PopperModifier = Partial< Modifier< unknown, Record< string, unknown > > >; + +export interface Callbacks { + onMinimize?: Callback; + onMaximize?: Callback; + onGoToStep?: Callback; + onNextStep?: Callback; + onPreviousStep?: Callback; + onStepViewOnce?: Callback; + onStepView?: Callback; +} + +export interface Options { + classNames?: string | string[]; + callbacks?: Callbacks; + /** An object to enable/disable/combine various tour effects, such as spotlight, overlay, and autoscroll */ + effects?: { + /** + * Adds a semi-transparent overlay and highlights the reference element + * when provided with a transparent box over it. The existence of this configuration + * key implies enabling the spotlight effect. + */ + spotlight?: { + /** An object that configures whether the user is allowed to interact with the referenced element during the tour */ + interactivity?: SpotlightInteractivityConfiguration; + /** CSS properties that configures the styles applied to the spotlight overlay */ + styles?: React.CSSProperties; + }; + /** Shows a little triangle that points to the referenced element. Defaults to true */ + arrowIndicator?: boolean; + /** + * Includes the semi-transparent overlay for all the steps Also blocks interactions for everything except the tour dialogues, + * including the referenced elements. Refer to spotlight interactivity configuration to affect this. + * + * Defaults to false, but if spotlight is enabled it implies this is enabled as well. + */ + overlay?: boolean; + /** Configures the autoscroll behaviour. Defaults to False. More information about the configuration at: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView */ + autoScroll?: ScrollIntoViewOptions | boolean; + /** Configures the behaviour for automatically resizing the tour kit elements (TourKitFrame and Spotlight). Defaults to disabled. */ + liveResize?: LiveResizeConfiguration; + }; + popperModifiers?: PopperModifier[]; + portalParentElement?: HTMLElement | null; +} + +export interface Config { + steps: Step[]; + renderers: { + tourStep: TourStepRenderer; + tourMinimized: MinimizedTourRenderer; + }; + closeHandler: CloseHandler; + isMinimized?: boolean; + options?: Options; + placement?: PopperJS.Placement; +} + +export type Tour = React.FunctionComponent< { config: Config } >; + +/************************ + * WPCOM variant types: * + ************************/ + +export type OnTourRateCallback = ( currentStepIndex: number, liked: boolean ) => void; + +export interface WpcomStep extends Step { + meta: { + heading: string | null; + descriptions: { + desktop: string | React.ReactElement | null; + mobile: string | React.ReactElement | null; + }; + imgSrc?: { + desktop?: { + src: string; + type: string; + }; + mobile?: { + src: string; + type: string; + }; + }; + imgLink?: { + href: string; + playable?: boolean; + onClick?: () => void; + }; + }; +} + +export interface WpcomTourStepRendererProps extends TourStepRendererProps { + steps: WpcomStep[]; +} + +export interface WpcomOptions extends Options { + tourRating?: { + enabled: boolean; + useTourRating?: () => 'thumbs-up' | 'thumbs-down' | undefined; + onTourRate?: ( rating: 'thumbs-up' | 'thumbs-down' ) => void; + }; +} + +export interface WpcomConfig extends Omit< Config, 'renderers' > { + steps: WpcomStep[]; + options?: WpcomOptions; +} + +export type WpcomTour = React.FunctionComponent< { config: WpcomConfig } >; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts new file mode 100644 index 0000000000000..3f00a820d1703 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts @@ -0,0 +1,16 @@ +import debugFactory from 'debug'; + +/** + * Helper to convert CSV of `classes` to an array. + * @param classes - String or array of classes to format. + * @returns Array of classes + */ +export function classParser( classes?: string | string[] ): string[] | null { + if ( classes?.length ) { + return classes.toString().split( ',' ); + } + + return null; +} + +export const debug = debugFactory( 'tour-kit' ); diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx new file mode 100644 index 0000000000000..ab1a14cf8568b --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx @@ -0,0 +1,111 @@ +import { debug } from '../utils'; +import type { ModifierArguments, Options, State } from '@popperjs/core'; +import type { Modifier } from 'react-popper'; + +// Adds the resizeObserver and mutationObserver properties to the popper effect function argument +type ModifierArgumentsWithObserversProp = ModifierArguments< Options > & { + state: State & { + elements: State[ 'elements' ] & { + reference: State[ 'elements' ][ 'reference' ] & { + [ key: symbol ]: { + resizeObserver: ResizeObserver; + mutationObserver: MutationObserver; + }; + }; + }; + }; +}; + +export interface LiveResizeConfiguration { + /** CSS Selector for the the DOM node (and children) to observe for mutations */ + rootElementSelector?: string; + /** True to enable update on reference element resize, defaults to false */ + resize?: boolean; + /** True to enable update on node and subtree mutation, defaults to false. May be performance intensive */ + mutation?: boolean; +} + +type liveResizeModifierFactory = ( + params: LiveResizeConfiguration | undefined +) => Modifier< 'liveResizeModifier', Record< string, unknown > >; + +/** + * Function that returns a Popper modifier that observes the specified root element as well as + * reference element for any changes. The reason for being a currying function is so that + * we can customise the root element selector, otherwise observing at a higher than necessary + * level might cause unnecessary performance penalties. + * + * The Popper modifier queues an asynchronous update on the Popper instance whenever either of the + * Observers trigger its callback. + * + * @param config - The config. + * @param config.rootElementSelector - The selector of the root element. + * @param config.mutation - Whether to mutate. + * @param config.resize - Whether to resize. + * @returns custom Popper modifier. + */ +export const liveResizeModifier: liveResizeModifierFactory = ( + { rootElementSelector, mutation = false, resize = false }: LiveResizeConfiguration = { + mutation: false, + resize: false, + } +) => ( { + name: 'liveResizeModifier', + enabled: true, + phase: 'main', + fn: () => { + return; + }, + effect: arg0 => { + try { + const { state, instance } = arg0 as ModifierArgumentsWithObserversProp; // augment types here because we are mutating the properties on the argument that is passed in + + const ObserversProp = Symbol(); // use a symbol here so that we don't clash with multiple poppers using this modifier on the same reference node + const { reference } = state.elements; + + reference[ ObserversProp ] = { + resizeObserver: new ResizeObserver( () => { + instance.update(); + } ), + + mutationObserver: new MutationObserver( () => { + instance.update(); + } ), + }; + + if ( resize ) { + if ( reference instanceof Element ) { + reference[ ObserversProp ].resizeObserver.observe( reference ); + } else { + debug( + 'Error: ResizeObserver does not work with virtual elements, Tour Kit will not resize automatically if the size of the referenced element changes.' + ); + } + } + + if ( mutation ) { + const rootElementNode = document.querySelector( rootElementSelector || '#wpwrap' ); + if ( rootElementNode instanceof Element ) { + reference[ ObserversProp ].mutationObserver.observe( rootElementNode, { + attributes: true, + characterData: true, + childList: true, + subtree: true, + } ); + } else { + debug( + `Error: ${ rootElementSelector } selector did not find a valid DOM element, Tour Kit will not update automatically if the DOM layout changes.` + ); + } + } + + return () => { + reference[ ObserversProp ].resizeObserver.disconnect(); + reference[ ObserversProp ].mutationObserver.disconnect(); + delete reference[ ObserversProp ]; + }; + } catch ( error ) { + debug( 'Error: Tour Kit live resize modifier failed unexpectedly:', error ); + } + }, +} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx new file mode 100644 index 0000000000000..1afd5ce37fca4 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx @@ -0,0 +1,50 @@ +import { Button, Flex } from '@wordpress/components'; +import { createInterpolateElement } from '@wordpress/element'; +import { sprintf } from '@wordpress/i18n'; +import { Icon, close } from '@wordpress/icons'; +import { useI18n } from '@wordpress/react-i18n'; +import maximize from '../icons/maximize'; +import type { MinimizedTourRendererProps } from '../../../types'; + +const WpcomTourKitMinimized: React.FunctionComponent< MinimizedTourRendererProps > = ( { + steps, + onMaximize, + onDismiss, + currentStepIndex, +} ) => { + const { __ } = useI18n(); + const lastStepIndex = steps.length - 1; + const page = currentStepIndex + 1; + const numberOfPages = lastStepIndex + 1; + + return ( + + + + + ); +}; + +export default WpcomTourKitMinimized; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss new file mode 100644 index 0000000000000..fe9a19fc4b508 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss @@ -0,0 +1,44 @@ +@import "@automattic/calypso-color-schemes"; + +.wpcom-tour-kit-pagination-control { + margin: 0; + display: flex; + justify-content: center; + width: 100%; + + li { + display: inline-flex; + margin: auto 4px; + height: 18px; + align-items: center; + border: none; + } + + li.pagination-control__last-item { + margin-left: auto; + height: 32px; + } + + button.pagination-control__page { + border: none; + padding: 0; + width: 6px; + height: 6px; + border-radius: 50%; + cursor: pointer; + transition: all 0.2s ease-in-out; + background-color: var(--color-neutral-10); + + &:hover { + background-color: var(--color-neutral-40); + } + + &:disabled { + cursor: default; + } + + &.is-current { + background-color: var(--wp-admin-theme-color); + } + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx new file mode 100644 index 0000000000000..5d9c48bca0fb1 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx @@ -0,0 +1,49 @@ +import { __, sprintf } from '@wordpress/i18n'; +import clsx from 'clsx'; +import './wpcom-tour-kit-pagination-control.scss'; + +interface Props { + onChange: ( page: number ) => void; + activePageIndex: number; + numberOfPages: number; + classNames?: string | string[]; + children?: React.ReactNode; +} + +const WpcomTourKitPaginationControl: React.FunctionComponent< Props > = ( { + activePageIndex, + numberOfPages, + onChange, + classNames, + children, +} ) => { + const classes = clsx( 'wpcom-tour-kit-pagination-control', classNames ); + + return ( +
    + { Array.from( { length: numberOfPages } ).map( index => ( +
  • +
  • + ) ) } + { children &&
  • { children }
  • } +
+ ); +}; + +export default WpcomTourKitPaginationControl; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx new file mode 100644 index 0000000000000..82c20f1410019 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx @@ -0,0 +1,73 @@ +import { Button } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { useI18n } from '@wordpress/react-i18n'; +import clsx from 'clsx'; +import { useTourKitContext } from '../../../index'; +import thumbsDown from '../icons/thumbs_down'; +import thumbsUp from '../icons/thumbs_up'; +import type { WpcomConfig } from '../../../index'; + +const WpcomTourKitRating: React.FunctionComponent = () => { + const [ tempRating, setTempRating ] = useState< 'thumbs-up' | 'thumbs-down' >(); + const context = useTourKitContext(); + const config = context.config as unknown as WpcomConfig; + const tourRating = config.options?.tourRating?.useTourRating?.() ?? tempRating; + const { __ } = useI18n(); + + let isDisabled = false; + + if ( ! config.options?.tourRating?.enabled ) { + return null; + } + + // check is on tempRating to allow rerating in a restarted tour + if ( ! isDisabled && tempRating !== undefined ) { + isDisabled = true; + } + + const rateTour = ( isThumbsUp: boolean ) => { + if ( isDisabled ) { + return; + } + + const rating = isThumbsUp ? 'thumbs-up' : 'thumbs-down'; + + if ( rating !== tourRating ) { + isDisabled = true; + setTempRating( rating ); + config.options?.tourRating?.onTourRate?.( rating ); + } + }; + + return ( + <> +

+ { __( 'Did you find this guide helpful?', 'jetpack-mu-wpcom' ) } +

+
+
+ + ); +}; + +export default WpcomTourKitRating; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx new file mode 100644 index 0000000000000..e324f0b2a4380 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx @@ -0,0 +1,62 @@ +import { Button } from '@wordpress/components'; +import { useI18n } from '@wordpress/react-i18n'; +import WpcomTourKitPaginationControl from './wpcom-tour-kit-pagination-control'; +import type { WpcomTourStepRendererProps } from '../../../types'; + +type Props = Omit< WpcomTourStepRendererProps, 'onMinimize' >; + +const WpcomTourKitStepCardNavigation: React.FunctionComponent< Props > = ( { + currentStepIndex, + onDismiss, + onGoToStep, + onNextStep, + onPreviousStep, + setInitialFocusedElement, + steps, +} ) => { + const { __ } = useI18n(); + const isFirstStep = currentStepIndex === 0; + const lastStepIndex = steps.length - 1; + + return ( + <> + + { isFirstStep ? ( +
+ + +
+ ) : ( +
+ + +
+ ) } +
+ + ); +}; + +export default WpcomTourKitStepCardNavigation; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx new file mode 100644 index 0000000000000..bfad1e0204dc7 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx @@ -0,0 +1,41 @@ +import { Button, Flex } from '@wordpress/components'; +import { close } from '@wordpress/icons'; +import { useI18n } from '@wordpress/react-i18n'; +import minimize from '../icons/minimize'; +import type { TourStepRendererProps } from '../../../types'; + +interface Props { + onMinimize: TourStepRendererProps[ 'onMinimize' ]; + onDismiss: TourStepRendererProps[ 'onDismiss' ]; +} + +const WpcomTourKitStepCardOverlayControls: React.FunctionComponent< Props > = ( { + onMinimize, + onDismiss, +} ) => { + const { __ } = useI18n(); + + return ( +
+ + + + +
+ ); +}; + +export default WpcomTourKitStepCardOverlayControls; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx new file mode 100644 index 0000000000000..743d4f1cce9ac --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx @@ -0,0 +1,109 @@ +import { Button, Card, CardBody, CardFooter, CardMedia } from '@wordpress/components'; +import { useViewportMatch } from '@wordpress/compose'; +import { Icon } from '@wordpress/icons'; +import { useI18n } from '@wordpress/react-i18n'; +import clsx from 'clsx'; +import WpcomTourKitRating from './wpcom-tour-kit-rating'; +import WpcomTourKitStepCardNavigation from './wpcom-tour-kit-step-card-navigation'; +import WpcomTourKitStepCardOverlayControls from './wpcom-tour-kit-step-card-overlay-controls'; +import type { WpcomTourStepRendererProps } from '../../../types'; + +const WpcomTourKitStepCard: React.FunctionComponent< WpcomTourStepRendererProps > = ( { + steps, + currentStepIndex, + onMinimize, + onDismiss, + onGoToStep, + onNextStep, + onPreviousStep, + setInitialFocusedElement, +} ) => { + const { __ } = useI18n(); + const lastStepIndex = steps.length - 1; + const { descriptions, heading, imgSrc, imgLink } = steps[ currentStepIndex ].meta; + const isLastStep = currentStepIndex === lastStepIndex; + const isMobile = useViewportMatch( 'mobile', '<' ); + const description = descriptions[ isMobile ? 'mobile' : 'desktop' ] ?? descriptions.desktop; + + return ( + + + { imgSrc && ( + + + { imgSrc.mobile && ( + + ) } + { + + { imgLink && ( + + + + + } + size={ 27 } + /> + + ) } + + ) } + +

{ heading }

+

+ { description } + { isLastStep ? ( + + ) : null } +

+
+ + { isLastStep ? ( + + ) : ( + + ) } + +
+ ); +}; + +export default WpcomTourKitStepCard; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx new file mode 100644 index 0000000000000..24064230211d2 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx @@ -0,0 +1,28 @@ +import WpcomTourKitStepCard from './wpcom-tour-kit-step-card'; +import type { WpcomTourStepRendererProps } from '../../../types'; + +const WpcomTourKitStep: React.FunctionComponent< WpcomTourStepRendererProps > = ( { + steps, + currentStepIndex, + onDismiss, + onNextStep, + onPreviousStep, + onMinimize, + setInitialFocusedElement, + onGoToStep, +} ) => { + return ( + + ); +}; + +export default WpcomTourKitStep; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx new file mode 100644 index 0000000000000..863d20ac6745d --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx @@ -0,0 +1,29 @@ +import TourKit from '../../../components/tour-kit'; +import usePrefetchTourAssets from '../hooks/use-prefetch-tour-assets'; +import WpcomTourKitMinimized from './wpcom-tour-kit-minimized'; +import WpcomTourKitStep from './wpcom-tour-kit-step'; +import '../styles.scss'; +import type { WpcomConfig, TourStepRenderer } from '../../../types'; + +interface Props { + config: WpcomConfig; +} + +const WpcomTourKit: React.FunctionComponent< Props > = ( { config } ) => { + usePrefetchTourAssets( config.steps ); + + return ( + + ); +}; + +export default WpcomTourKit; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx new file mode 100644 index 0000000000000..76db7335bf3eb --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx @@ -0,0 +1,16 @@ +import { useEffect } from '@wordpress/element'; +import type { WpcomStep } from '../../../types'; + +/** + * The hook to prefetch the assets of the tour + * + * @param steps - The steps that require assets. + */ +export default function usePrefetchTourAssets( steps: WpcomStep[] ): void { + useEffect( () => { + steps.forEach( step => { + step.meta.imgSrc?.mobile && ( new window.Image().src = step.meta.imgSrc.mobile.src ); + step.meta.imgSrc?.desktop && ( new window.Image().src = step.meta.imgSrc.desktop.src ); + } ); + }, [ steps ] ); +} diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx new file mode 100644 index 0000000000000..0d0ee00282e4c --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx @@ -0,0 +1,13 @@ +import { SVG, Path } from '@wordpress/primitives'; + +const minimize = ( + + + +); + +export default minimize; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx new file mode 100644 index 0000000000000..e844c2cd1fc64 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx @@ -0,0 +1,14 @@ +import { SVG, Path } from '@wordpress/primitives'; + +const minimize = ( + + + +); + +export default minimize; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx new file mode 100644 index 0000000000000..784e6668db965 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx @@ -0,0 +1,14 @@ +import { SVG, Path } from '@wordpress/primitives'; + +const thumbsDown = ( + + + +); + +export default thumbsDown; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx new file mode 100644 index 0000000000000..d57b147512eac --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx @@ -0,0 +1,14 @@ +import { SVG, Path } from '@wordpress/primitives'; + +const thumbsUp = ( + + + +); + +export default thumbsUp; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts new file mode 100644 index 0000000000000..f092f4ed3fa54 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts @@ -0,0 +1,2 @@ +export { default } from './components/wpcom-tour-kit'; +export { default as usePrefetchTourAssets } from './hooks/use-prefetch-tour-assets'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss new file mode 100644 index 0000000000000..56483b6839201 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss @@ -0,0 +1,232 @@ +@use "sass:math"; +@import "@wordpress/base-styles/colors"; +@import "@wordpress/base-styles/mixins"; +@import "@wordpress/base-styles/variables"; +@import "@wordpress/base-styles/z-index"; + +$wpcom-tour-kit-step-card-overlay-controls-button-bg-color: #32373c; // former $dark-gray-700. TODO: replace with standard color + +.wpcom-tour-kit-minimized { + border-radius: 2px; + box-shadow: + 0 2px 6px rgba(60, 66, 87, 0.08), + 0 0 0 1px rgba(60, 66, 87, 0.16), + 0 1px 1px rgba(0, 0, 0, 0.08); + background-color: $white; + color: $black; + + .components-button { + height: 44px; + + .wpcom-tour-kit-minimized__tour-index { + color: $gray-600; + } + + svg { + color: #50575e; + } + + &:hover { + .wpcom-tour-kit-minimized__tour-index, + svg { + color: inherit; + } + } + } +} + +.wpcom-tour-kit-step-card__heading { + font-size: 1.125rem; /* stylelint-disable-line scales/font-sizes */ + margin: 0.5rem 0; +} + +.wpcom-tour-kit-step-card__description { + font-size: 0.875rem; + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + line-height: 1.5rem; + margin: 0; + + .components-button { + height: auto; + line-height: 1; + text-decoration: underline; + padding: 0 0 0 4px; + } +} + +// @todo clk - update? +.wpcom-tour-kit .tour-kit-frame__container { + box-shadow: none; +} + +.wpcom-tour-kit-step-card { + width: 416px; + max-width: 92vw; + + &.wpcom-tour-kit-step-card.is-elevated { + box-shadow: rgba(0, 0, 0, 0.1) 0 0 0 1px, rgba(0, 0, 0, 0.1) 0 2px 4px 0; + } + + &.components-card { + border: none; + border-radius: 4px; + box-shadow: none; + } + + .components-card__body { + min-height: 114px; + } + + .components-card__body, + .components-card__footer { + border-top: none; + padding: $grid-unit-20 !important; + } + + .components-card__footer { + .wpcom-tour-kit-rating__end-text { + color: $gray-600; + font-size: 0.875rem; + font-style: italic; + } + + .wpcom-tour-kit-rating__end-icon.components-button.has-icon { + background-color: #f6f7f7; + border-radius: 50%; + color: $gray-600; + margin-left: 8px; + + path { + fill: $gray-600; + } + + &.active { + background-color: $black; + opacity: 1; + + path { + fill: $white; + } + } + } + } + + .components-card__media { + height: 0; + padding-top: math.percentage(math.div(math.ceil(math.div(1, 1.53) * 100), 100)); // img width:height ratio (1:1.53) + position: relative; + width: 100%; + + img { + left: 0; + position: absolute; + top: 0; + width: 100%; + } + } + + .components-guide__page-control { + margin: 0; + + .components-button { + min-width: auto; + &.has-icon { + padding: 3px; + } + } + + li { + margin-bottom: 0; + } + } +} + +.wpcom-tour-kit-step-card-overlay-controls__minimize-icon svg { + position: relative; + left: -2px; +} + +.wpcom-tour-kit-step-card-overlay-controls { + left: 0; + padding: $grid-unit-15; + right: 0; + z-index: 1; // z-index is needed because overlay controls are written before components-card__media, and so ends up under the image + + .components-button { + width: 32px; + min-width: 32px; + height: 32px; + background: $wpcom-tour-kit-step-card-overlay-controls-button-bg-color; + transition: opacity 200ms; + opacity: 0.7; + + &:active { + opacity: 0.9; + } + } + + @media (hover: hover) and (pointer: fine) { + // styles only applicable for hoverable viewports with precision pointing devices connected (eg: mouse) + .components-button { + opacity: 0; + } + + .tour-kit-frame__container:hover &, + .tour-kit-frame__container:focus-within & { + .components-button { + opacity: 0.7; + + &:hover, + &:focus { + opacity: 0.9; + } + } + } + } +} + +.wpcom-tour-kit-step-card-navigation__next-btn { + margin-left: $grid-unit-15; + justify-content: center; + min-width: 85px; +} + +.wpcom-tour-kit-step-card__media { + position: relative; +} + +// TODO: Remove once @wordpress/components/src/card/styles/card-styles.js is updated +.wpcom-tour-kit-step-card__media img { + display: block; + height: auto; + max-width: 100%; + width: 100%; +} + +.wpcom-tour-kit-step-card__media-link { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + + svg { + display: none; + transition: transform 0.15s ease-in-out; + + &:hover { + transform: scale(1.05); + } + } + + &--playable { + background-color: rgba(0, 0, 0, 0.5); + + svg { + display: block; + } + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js index 233aa88660e87..c798d65443038 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js @@ -1,8 +1,3 @@ -/*** THIS MUST BE THE FIRST THING EVALUATED IN THIS SCRIPT *****/ -import './public-path'; - -/* eslint-disable wpcalypso/jsx-classname-namespace */ - import { LocaleProvider, i18nDefaultLocaleSlug } from '@automattic/i18n-utils'; import { Guide, GuidePage } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; @@ -11,9 +6,9 @@ import { applyFilters } from '@wordpress/hooks'; import { registerPlugin } from '@wordpress/plugins'; import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; import { getQueryArg } from '@wordpress/url'; -import { ShouldShowFirstPostPublishedModalProvider } from '../../dotcom-fse/lib/first-post-published-modal/should-show-first-post-published-modal-context'; -import { HasSeenSellerCelebrationModalProvider } from '../../dotcom-fse/lib/seller-celebration-modal/has-seen-seller-celebration-modal-context'; -import { HasSeenVideoCelebrationModalProvider } from '../../dotcom-fse/lib/video-celebration-modal/has-seen-video-celebration-modal-context'; +import { HasSeenSellerCelebrationModalProvider } from '../../../common/tour-kit/contexts/has-seen-seller-celebration-modal-context'; +import { HasSeenVideoCelebrationModalProvider } from '../../../common/tour-kit/contexts/has-seen-video-celebration-modal-context'; +import { ShouldShowFirstPostPublishedModalProvider } from '../../../common/tour-kit/contexts/should-show-first-post-published-modal-context'; import { BloggingPromptsModal } from './blogging-prompts-modal'; import DraftPostModal from './draft-post-modal'; import FirstPostPublishedModal from './first-post-published-modal'; @@ -42,7 +37,7 @@ try { } /** - * + * The WelcomeTour component */ function WelcomeTour() { const [ showDraftPostModal ] = useState( diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx index 404f755a2a573..91877760e0203 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx @@ -4,9 +4,9 @@ import { useEffect, useRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; import React from 'react'; +import { useSiteIntent } from '../../../../common/tour-kit'; +import { useShouldShowFirstPostPublishedModal } from '../../../../common/tour-kit/contexts/should-show-first-post-published-modal-context'; import { wpcomTrackEvent } from '../../../../common/tracks'; -import { useShouldShowFirstPostPublishedModal } from '../../../dotcom-fse/lib/first-post-published-modal/should-show-first-post-published-modal-context'; -import useSiteIntent from '../../../dotcom-fse/lib/site-intent/use-site-intent'; import NuxModal from '../nux-modal'; import postPublishedImage from './images/post-published.svg'; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/public-path.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/public-path.js deleted file mode 100644 index 6bab2016a50ad..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/public-path.js +++ /dev/null @@ -1,11 +0,0 @@ -/* exported __webpack_public_path__ */ -/* global __webpack_public_path__ */ - -/** - * Dynamically set WebPack's publicPath so that split assets can be found. - * @see https://webpack.js.org/guides/public-path/#on-the-fly - */ -if ( typeof window === 'object' && window.wpcomBlockEditorNuxAssetsUrl ) { - // eslint-disable-next-line no-global-assign - __webpack_public_path__ = window.wpcomBlockEditorNuxAssetsUrl; -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx index e8c3f727880b4..d58a8cc883a79 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx @@ -2,10 +2,9 @@ import { Button } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { useState, useRef, useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { useSiteIntent, useShouldShowSellerCelebrationModal } from '../../../../common/tour-kit'; +import { useHasSeenSellerCelebrationModal } from '../../../../common/tour-kit/contexts/has-seen-seller-celebration-modal-context'; import { wpcomTrackEvent } from '../../../../common/tracks'; -import { useHasSeenSellerCelebrationModal } from '../../../dotcom-fse/lib/seller-celebration-modal/has-seen-seller-celebration-modal-context'; -import useShouldShowSellerCelebrationModal from '../../../dotcom-fse/lib/seller-celebration-modal/use-should-show-seller-celebration-modal'; -import useSiteIntent from '../../../dotcom-fse/lib/site-intent/use-site-intent'; import NuxModal from '../nux-modal'; import contentSubmittedImage from './images/product-published.svg'; import './style.scss'; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx new file mode 100644 index 0000000000000..bd077bf7625cc --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx @@ -0,0 +1,47 @@ +import { Button } from '@wordpress/components'; +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +interface ClipboardButtonProps { + className?: string; + compact?: boolean; + disabled?: boolean; + primary?: boolean; + scary?: boolean; + busy?: boolean; + borderless?: boolean; + plain?: boolean; + transparent?: boolean; + text: string | null; + onCopy?: () => void; + onMouseLeave?: () => void; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const noop = () => {}; + +const ClipboardButton = forwardRef< + HTMLButtonElement, + React.PropsWithChildren< ClipboardButtonProps > +>( ( { className, text, onCopy = noop, ...rest }, ref ) => { + /** + * The copy handler + */ + function onCopyHandler() { + if ( text ) { + navigator.clipboard.writeText( text ); + onCopy(); + } + } + + return ( +