diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index f8ff0441a95b7b..e4d9da813417eb 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -37,7 +37,7 @@ jobs: uses: ./.github/setup-node - name: Restore tests setup cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: | ~/.appium @@ -52,7 +52,7 @@ jobs: # AVD cache disabled as it caused emulator termination to hang indefinitely. # https://github.com/ReactiveCircus/android-emulator-runner/issues/385 # - name: AVD cache - # uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + # uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 # id: avd-cache # with: # path: | diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index d28ee65c719e43..e1e7fd8c755c97 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -42,7 +42,7 @@ jobs: uses: ./.github/setup-node - name: Restore tests setup cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: | ~/.appium @@ -55,7 +55,7 @@ jobs: run: find package-lock.json packages/react-native-editor/ios packages/react-native-aztec/ios packages/react-native-bridge/ios -type f -print0 | sort -z | xargs -0 shasum | tee ios-checksums.txt - name: Restore build cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: | packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app @@ -63,7 +63,7 @@ jobs: key: ${{ runner.os }}-ios-build-${{ matrix.xcode }}-${{ matrix.device }}-${{ hashFiles('ios-checksums.txt') }} - name: Restore pods cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: | packages/react-native-editor/ios/Pods diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 46aa109c23e658..efc7ef76f8c648 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -296,7 +296,7 @@ jobs: run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> $GITHUB_OUTPUT - name: Cache PHPCS scan cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: .cache/phpcs.json key: ${{ runner.os }}-date-${{ steps.get-date.outputs.date }}-phpcs-cache-${{ hashFiles('**/composer.json', 'phpcs.xml.dist') }} diff --git a/backport-changelog/6.8/7976.md b/backport-changelog/6.8/7976.md new file mode 100644 index 00000000000000..e2942d5e4fbe15 --- /dev/null +++ b/backport-changelog/6.8/7976.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7976 + +* https://github.com/WordPress/gutenberg/pull/67716 \ No newline at end of file diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 083ce3516b71af..778dcdbec78d96 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1319,6 +1319,8 @@ public function get_settings() { * - `variables`: only the CSS Custom Properties for presets & custom ones. * - `styles`: only the styles section in theme.json. * - `presets`: only the classes for the presets. + * - `base-layout-styles`: only the base layout styles. + * - `custom-css`: only the custom CSS. * @param array $origins A list of origins to include. By default it includes VALID_ORIGINS. * @param array $options An array of options for now used for internal purposes only (may change without notice). * The options currently supported are: diff --git a/lib/global-styles-and-settings.php b/lib/global-styles-and-settings.php index c4446cf29cf011..3ff5e6cb135e18 100644 --- a/lib/global-styles-and-settings.php +++ b/lib/global-styles-and-settings.php @@ -9,7 +9,7 @@ * Returns the stylesheet resulting of merging core, theme, and user data. * * @param array $types Types of styles to load. Optional. - * It accepts as values: 'variables', 'presets', 'styles', 'base-layout-styles. + * See {@see 'WP_Theme_JSON::get_stylesheet'} for all valid types. * If empty, it'll load the following: * - for themes without theme.json: 'variables', 'presets', 'base-layout-styles'. * - for themes with theme.json: 'variables', 'presets', 'styles'. @@ -142,6 +142,8 @@ function gutenberg_get_global_settings( $path = array(), $context = array() ) { /** * Gets the global styles custom css from theme.json. * + * @deprecated Gutenberg 18.6.0 Use {@see 'gutenberg_get_global_stylesheet'} instead for top-level custom CSS, or {@see 'WP_Theme_JSON_Gutenberg::get_styles_for_block'} for block-level custom CSS. + * * @return string */ function gutenberg_get_global_styles_custom_css() { diff --git a/packages/block-editor/src/components/block-lock/modal.js b/packages/block-editor/src/components/block-lock/modal.js index 3be23f6adde146..df267e97165e36 100644 --- a/packages/block-editor/src/components/block-lock/modal.js +++ b/packages/block-editor/src/components/block-lock/modal.js @@ -99,9 +99,7 @@ export default function BlockLockModal( { clientId, onClose } ) { >
- { __( - 'Choose specific attributes to restrict or lock all available options.' - ) } + { __( 'Select the features you want to lock' ) } { /* * Disable reason: The `list` ARIA role is redundant but @@ -137,7 +135,7 @@ export default function BlockLockModal( { clientId, onClose } ) {
  • setLock( ( prevLock ) => ( { @@ -159,7 +157,7 @@ export default function BlockLockModal( { clientId, onClose } ) {
  • setLock( ( prevLock ) => ( { @@ -178,7 +176,7 @@ export default function BlockLockModal( { clientId, onClose } ) {
  • setLock( ( prevLock ) => ( { diff --git a/packages/block-editor/src/components/block-patterns-list/stories/fixtures.js b/packages/block-editor/src/components/block-patterns-list/stories/fixtures.js index 0fd895bbe1716d..7825ad0d1391c6 100644 --- a/packages/block-editor/src/components/block-patterns-list/stories/fixtures.js +++ b/packages/block-editor/src/components/block-patterns-list/stories/fixtures.js @@ -530,6 +530,7 @@ export default [ background: '#000000', }, }, + tagName: 'hr', }, innerBlocks: [], originalContent: diff --git a/packages/block-editor/src/components/spacing-sizes-control/style.scss b/packages/block-editor/src/components/spacing-sizes-control/style.scss index a387e5369d01ed..26f3dc586bb54b 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/style.scss +++ b/packages/block-editor/src/components/spacing-sizes-control/style.scss @@ -4,40 +4,11 @@ margin-bottom: 0; } - .is-marked { - .components-range-control__track { - transition: width ease 0.1s; - @include reduce-motion("transition"); - } - - .components-range-control__thumb-wrapper { - transition: left ease 0.1s; - @include reduce-motion("transition"); - } - } - .spacing-sizes-control__range-control, .spacing-sizes-control__custom-value-range { flex: 1; margin-bottom: 0; // Needed for some instances of the range control, such as the Spacer block. } - - .components-range-control__mark { - transform: translateX(-50%); - height: $grid-unit-05; - width: math.div($grid-unit-05, 2); - background-color: $white; - z-index: 1; - top: -#{$grid-unit-05}; - } - - .components-range-control__marks { - margin-top: 17px; - } - - .components-range-control__thumb-wrapper { - z-index: 3; - } } .spacing-sizes-control__header { diff --git a/packages/block-editor/src/components/warning/index.js b/packages/block-editor/src/components/warning/index.js index 8b6279bef6044d..17a014107b43af 100644 --- a/packages/block-editor/src/components/warning/index.js +++ b/packages/block-editor/src/components/warning/index.js @@ -6,7 +6,6 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { Children } from '@wordpress/element'; import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { moreVertical } from '@wordpress/icons'; @@ -20,10 +19,10 @@ function Warning( { className, actions, children, secondaryActions } ) { { children }

    - { ( Children.count( actions ) > 0 || secondaryActions ) && ( + { ( actions?.length > 0 || secondaryActions ) && (
    - { Children.count( actions ) > 0 && - Children.map( actions, ( action, i ) => ( + { actions?.length > 0 && + actions.map( ( action, i ) => ( { it( 'should show primary actions', () => { render( - Click me }>Message + Click me ] }> + Message + ); expect( diff --git a/packages/block-editor/src/hooks/use-zoom-out.js b/packages/block-editor/src/hooks/use-zoom-out.js index bcf5d9ff882f7b..adcea8b605aeb7 100644 --- a/packages/block-editor/src/hooks/use-zoom-out.js +++ b/packages/block-editor/src/hooks/use-zoom-out.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; /** * Internal dependencies @@ -12,32 +12,64 @@ import { unlock } from '../lock-unlock'; /** * A hook used to set the editor mode to zoomed out mode, invoking the hook sets the mode. + * Concepts: + * - If we most recently changed the zoom level for them (in or out), we always resetZoomLevel() level when unmounting. + * - If the user most recently changed the zoom level (manually toggling), we do nothing when unmounting. * - * @param {boolean} zoomOut If we should enter into zoomOut mode or not + * @param {boolean} enabled If we should enter into zoomOut mode or not */ -export function useZoomOut( zoomOut = true ) { +export function useZoomOut( enabled = true ) { const { setZoomLevel, resetZoomLevel } = unlock( useDispatch( blockEditorStore ) ); - const { isZoomOut } = unlock( useSelect( blockEditorStore ) ); + /** + * We need access to both the value and the function. The value is to trigger a useEffect hook + * and the function is to check zoom out within another hook without triggering a re-render. + */ + const { isZoomedOut, isZoomOut } = useSelect( ( select ) => { + const { isZoomOut: _isZoomOut } = unlock( select( blockEditorStore ) ); + return { + isZoomedOut: _isZoomOut(), + isZoomOut: _isZoomOut, + }; + }, [] ); + + const controlZoomLevelRef = useRef( false ); + const isEnabledRef = useRef( enabled ); + + /** + * This hook tracks if the zoom state was changed manually by the user via clicking + * the zoom out button. We only want this to run when isZoomedOut changes, so we use + * a ref to track the enabled state. + */ useEffect( () => { - const isZoomOutOnMount = isZoomOut(); + // If the zoom state changed (isZoomOut) and it does not match the requested zoom + // state (zoomOut), then it means the user manually changed the zoom state while + // this hook was mounted, and we should no longer control the zoom state. + if ( isZoomedOut !== isEnabledRef.current ) { + controlZoomLevelRef.current = false; + } + }, [ isZoomedOut ] ); - return () => { - if ( isZoomOutOnMount ) { + useEffect( () => { + isEnabledRef.current = enabled; + + if ( enabled !== isZoomOut() ) { + controlZoomLevelRef.current = true; + + if ( enabled ) { setZoomLevel( 'auto-scaled' ); } else { resetZoomLevel(); } - }; - }, [] ); - - useEffect( () => { - if ( zoomOut ) { - setZoomLevel( 'auto-scaled' ); - } else { - resetZoomLevel(); } - }, [ zoomOut, setZoomLevel, resetZoomLevel ] ); + + return () => { + // If we are controlling zoom level and are zoomed out, reset the zoom level. + if ( controlZoomLevelRef.current && isZoomOut() ) { + resetZoomLevel(); + } + }; + }, [ enabled, isZoomOut, resetZoomLevel, setZoomLevel ] ); } diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 1fd87cc958352f..2d2761924fb633 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -10,6 +10,7 @@ - `GradientPicker`: Add `enableAlpha` prop ([#66974](https://github.com/WordPress/gutenberg/pull/66974)) - `CustomGradientPicker`: Add `enableAlpha` prop ([#66974](https://github.com/WordPress/gutenberg/pull/66974)) +- `RangeControl`: Update the design of the range control marks ([#67611](https://github.com/WordPress/gutenberg/pull/67611)) ### Deprecations diff --git a/packages/components/src/date-time/stories/date-time.story.tsx b/packages/components/src/date-time/stories/date-time.story.tsx index 0c6af3f53fa195..e240b9da470563 100644 --- a/packages/components/src/date-time/stories/date-time.story.tsx +++ b/packages/components/src/date-time/stories/date-time.story.tsx @@ -51,6 +51,9 @@ const Template: StoryFn< typeof DateTimePicker > = ( { }; export const Default: StoryFn< typeof DateTimePicker > = Template.bind( {} ); +Default.args = { + currentDate: new Date(), +}; export const WithEvents: StoryFn< typeof DateTimePicker > = Template.bind( {} ); WithEvents.args = { diff --git a/packages/components/src/date-time/stories/date.story.tsx b/packages/components/src/date-time/stories/date.story.tsx index ae4816809f7865..d305edf7a29e1c 100644 --- a/packages/components/src/date-time/stories/date.story.tsx +++ b/packages/components/src/date-time/stories/date.story.tsx @@ -51,6 +51,9 @@ const Template: StoryFn< typeof DatePicker > = ( { }; export const Default: StoryFn< typeof DatePicker > = Template.bind( {} ); +Default.args = { + currentDate: new Date(), +}; export const WithEvents: StoryFn< typeof DatePicker > = Template.bind( {} ); WithEvents.args = { diff --git a/packages/components/src/date-time/stories/time.story.tsx b/packages/components/src/date-time/stories/time.story.tsx index 12e5574115a6c3..5497b1e84138c0 100644 --- a/packages/components/src/date-time/stories/time.story.tsx +++ b/packages/components/src/date-time/stories/time.story.tsx @@ -52,6 +52,9 @@ const Template: StoryFn< typeof TimePicker > = ( { }; export const Default: StoryFn< typeof TimePicker > = Template.bind( {} ); +Default.args = { + currentTime: new Date(), +}; const TimeInputTemplate: StoryFn< typeof TimePicker.TimeInput > = ( args ) => { return ; diff --git a/packages/components/src/disabled/stories/index.story.tsx b/packages/components/src/disabled/stories/index.story.tsx index 7a42da71f8efe9..f017305507814e 100644 --- a/packages/components/src/disabled/stories/index.story.tsx +++ b/packages/components/src/disabled/stories/index.story.tsx @@ -82,7 +82,7 @@ Default.args = { export const ContentEditable: StoryFn< typeof Disabled > = ( args ) => { return ( -
    +
    contentEditable
    diff --git a/packages/components/src/popover/stories/index.story.tsx b/packages/components/src/popover/stories/index.story.tsx index cf1bd5135ad9fd..1e1d4225bd0233 100644 --- a/packages/components/src/popover/stories/index.story.tsx +++ b/packages/components/src/popover/stories/index.story.tsx @@ -58,7 +58,9 @@ const meta: Meta< typeof Popover > = { export default meta; const PopoverWithAnchor = ( args: PopoverProps ) => { - const anchorRef = useRef( null ); + const [ popoverAnchor, setPopoverAnchor ] = useState< Element | null >( + null + ); return (
    { >

    Popover's anchor

    - +
    ); }; diff --git a/packages/components/src/range-control/mark.tsx b/packages/components/src/range-control/mark.tsx index a17665bb628aba..8692db679f2562 100644 --- a/packages/components/src/range-control/mark.tsx +++ b/packages/components/src/range-control/mark.tsx @@ -38,7 +38,6 @@ export default function RangeMark( { ...otherProps } aria-hidden="true" className={ classes } - isFilled={ isFilled } style={ style } /> { label && ( diff --git a/packages/components/src/range-control/styles/range-control-styles.ts b/packages/components/src/range-control/styles/range-control-styles.ts index 6e9c68ace97532..d943ca472911ed 100644 --- a/packages/components/src/range-control/styles/range-control-styles.ts +++ b/packages/components/src/range-control/styles/range-control-styles.ts @@ -130,6 +130,10 @@ export const Track = styled.span` margin-top: ${ ( rangeHeightValue - railHeight ) / 2 }px; top: 0; + @media not ( prefers-reduced-motion ) { + transition: width ease 0.1s; + } + ${ trackBackgroundColor }; `; @@ -139,28 +143,18 @@ export const MarksWrapper = styled.span` position: relative; width: 100%; user-select: none; + margin-top: 17px; `; -const markFill = ( { disabled, isFilled }: RangeMarkProps ) => { - let backgroundColor = isFilled ? 'currentColor' : COLORS.gray[ 300 ]; - - if ( disabled ) { - backgroundColor = COLORS.gray[ 400 ]; - } - - return css( { - backgroundColor, - } ); -}; - export const Mark = styled.span` - height: ${ thumbSize }px; - left: 0; position: absolute; - top: 9px; - width: 1px; - - ${ markFill }; + left: 0; + top: -4px; + height: 4px; + width: 2px; + transform: translateX( -50% ); + background-color: ${ COLORS.ui.background }; + z-index: 1; `; const markLabelFill = ( { isFilled }: RangeMarkProps ) => { @@ -173,7 +167,7 @@ export const MarkLabel = styled.span` color: ${ COLORS.gray[ 300 ] }; font-size: 11px; position: absolute; - top: 22px; + top: 8px; white-space: nowrap; ${ rtl( { left: 0 } ) }; @@ -207,6 +201,11 @@ export const ThumbWrapper = styled.span` user-select: none; width: ${ thumbSize }px; border-radius: ${ CONFIG.radiusRound }; + z-index: 3; + + @media not ( prefers-reduced-motion ) { + transition: left ease 0.1s; + } ${ thumbColor }; ${ rtl( { marginLeft: -10 } ) }; diff --git a/packages/components/src/scrollable/stories/index.story.tsx b/packages/components/src/scrollable/stories/index.story.tsx index 318a841f229788..4970b3720e8a07 100644 --- a/packages/components/src/scrollable/stories/index.story.tsx +++ b/packages/components/src/scrollable/stories/index.story.tsx @@ -70,6 +70,7 @@ const Template: StoryFn< typeof Scrollable > = ( { ...args } ) => { } } type="text" value="Focus me" + readOnly /> diff --git a/packages/components/src/theme/stories/index.story.tsx b/packages/components/src/theme/stories/index.story.tsx index 67eec72533ff3f..8ef87cbe8ddb41 100644 --- a/packages/components/src/theme/stories/index.story.tsx +++ b/packages/components/src/theme/stories/index.story.tsx @@ -37,7 +37,7 @@ export const Default = Template.bind( {} ); Default.args = {}; export const Nested: StoryFn< typeof Theme > = ( args ) => ( - + diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md index 651d87268c28e1..6f74a13d8f197a 100644 --- a/packages/dataviews/README.md +++ b/packages/dataviews/README.md @@ -1,6 +1,6 @@ # The `@wordpress/dataviews` package -The DataViews package offers two React components and a few utilites to work with a list of data: +The DataViews package offers two React components and a few utilities to work with a list of data: - `DataViews`: to render the dataset using different types of layouts (table, grid, list) and interaction capabilities (search, filters, sorting, etc.). - `DataForm`: to edit the items of the dataset. @@ -15,7 +15,7 @@ npm install @wordpress/dataviews --save ## `DataViews` - + **Important note** If you're trying to use the `DataViews` component in a WordPress plugin or theme and you're building your scripts using the `@wordpress/scripts` package, you need to import the components from `@wordpress/dataviews/wp` instead of `@wordpress/dataviews`. @@ -68,13 +68,13 @@ const data = [ ]; ``` -The data can come from anywhere, from a static JSON file to a dynamic source like a HTTP Request. It's the consumer's responsiblity to query the data source appropiately and update the dataset based on the user's choices for sorting, filtering, etc. +The data can come from anywhere, from a static JSON file to a dynamic source like an HTTP Request. It's the consumer's responsibility to query the data source appropriately and update the dataset based on the user's choices for sorting, filtering, etc. Each record should have an `id` that identifies them uniquely. If they don't, the consumer should provide the `getItemId` property to `DataViews`: a function that returns an unique identifier for the record. #### `getItemId`: `function` -Function that receives an item and returns an unique identifier for it. +A function that receives an item and returns a unique identifier for it. It's optional. The field will get a default implementation by `DataViews` that returns the value of the `item[ id ]`. @@ -185,9 +185,9 @@ Properties: - `field`: the field used for sorting the dataset. - `direction`: the direction to use for sorting, one of `asc` or `desc`. -- `titleField`: The id of the field reprensenting the title of the record. -- `mediaField`: The id of the field reprensenting the media of the record. -- `descriptionField`: The id of field the reprensenting the description of the record. +- `titleField`: The id of the field representing the title of the record. +- `mediaField`: The id of the field representing the media of the record. +- `descriptionField`: The id of the field representing the description of the record. - `showTitle`: Whether the title should be shown in the UI. `true` by default. - `showMedia`: Whether the media should be shown in the UI. `true` by default. - `showDescription`: Whether the description should be shown in the UI. `true` by default. @@ -205,7 +205,7 @@ Properties: Callback executed when the view has changed. It receives the new view object as a parameter. -The view is a representation of the visible state of the dataset: what type of layout is used to display it (table, grid, etc.), how the dataset is filtered, how it is sorted or paginated. It's the consumer's responsibility to use the view config to query the data provider and make sure the user decisions (sort, pagination, filters, etc.) are respected. +The view is a representation of the visible state of the dataset: what type of layout is used to display it (table, grid, etc.), how the dataset is filtered, and how it is sorted or paginated. The consumer is responsible for using the view config to query the data provider and ensure the user decisions (sort, pagination, filters, etc.) are respected. The following example shows how a view object is used to query the WordPress REST API via the entities abstraction. The same can be done with any other data provider. @@ -337,7 +337,7 @@ Whether the data is loading. `false` by default. #### `defaultLayouts`: `Record< string, view >` -This property provides layout information about the view types that are active. If empty, enables all layout types (see "Layout Types") with empty layout data. +This property provides layout information about active view types. If empty, this enables all layout types (see "Layout Types") with empty layout data. For example, this is how you'd enable only the table view type: @@ -358,17 +358,17 @@ The `defaultLayouts` property should be an object that includes properties named The list of selected items' ids. -If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves as a controlled component, otherwise, it behaves like an uncontrolled component. +If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves like a controlled component. Otherwise, it behaves like an uncontrolled component. #### `onChangeSelection`: `function` -Callback that signals the user selected one of more items. It receives the list of selected items' ids as a parameter. +Callback that signals the user selected one of more items. It receives the list of selected items' IDs as a parameter. -If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves as a controlled component, otherwise, it behaves like an uncontrolled component. +If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves like a controlled component. Otherwise, it behaves like an uncontrolled component. ### `isItemClickable`: `function` -A function that determines if a media field or a primary field are clickable. It receives an item as an argument and returns a boolean value indicating whether the item can be clicked. +A function that determines if a media field or a primary field is clickable. It receives an item as an argument and returns a boolean value indicating whether the item can be clicked. ### `onClickItem`: `function` @@ -405,7 +405,7 @@ const Example = () => { A single item to be edited. -It can be think of as a single record coming from the `data` property of `DataViews` — though it doesn't need to be. It can be totally separated or a mix of records if your app supports bulk editing. +It can be thought of as a single record coming from the `data` property of `DataViews` — though it doesn't need to be. It can be totally separated or a mix of records if your app supports bulk editing. #### `fields`: `Object[]` @@ -500,7 +500,7 @@ Returns an object containing: ### `isItemValid` -Utility to determine whether or not the given item's value is valid according to the current fields and form config. +Utility is used to determine whether or not the given item's value is valid according to the current fields and form configuration. Parameters: @@ -687,7 +687,7 @@ Example: Field type. One of `text`, `integer`, `datetime`. -If a field declares a `type`, it gets default implementations for the `sort`, `isValid`, and `Edit` functions. They will overriden if the field provides its own. +If a field declares a `type`, it gets default implementations for the `sort`, `isValid`, and `Edit` functions if no other values are specified. - Type: `string`. - Optional. @@ -732,7 +732,7 @@ Example: ### `getValue` -React component that returns the value of a field. This value is used in sorting the fields, or when filtering. +React component that returns the value of a field. This value is used to sort or filter the fields. - Type: React component. - Optional. @@ -977,13 +977,13 @@ Example: ### `elements` -List of valid values for a field. If provided, it creates a DataViews' filter for the field. DataForm's edit control will use these values as well (see `Edit` field property). +List of valid values for a field. If provided, it creates a DataViews' filter for the field. DataForm's edit control will also use these values. (See `Edit` field property.) - Type: `array` of objects. - Optional. - Each object can have the following properties: - - `value`: required, the value to match against the field's value. - - `label`: required, the name to display to users. + - `value`: the value to match against the field's value. (Required) + - `label`: the name to display to users. (Required) - `description`: optional, a longer description of the item. Example: @@ -1006,7 +1006,7 @@ Configuration of the filters. - Type: `object`. - Optional. - Properties: - - `operators`: the list of operators supported by the field. See "operators" below. By default, a filter will support the `isAny` and `isNone` multi-selection operators. + - `operators`: the list of operators supported by the field. See "operators" below. A filter will support the `isAny` and `isNone` multi-selection operators by default. - `isPrimary`: boolean, optional. Indicates if the filter is primary. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter. Operators: @@ -1020,7 +1020,7 @@ Operators: | `isAll` | Multiple items | `AND`. The item's field has all of the values in the list. | Category is all: Book, Review, Science Fiction | | `isNotAll` | Multiple items | `NOT AND`. The item's field doesn't have all of the values in the list. | Category is not all: Book, Review, Science Fiction | -`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. By default, a filter with no operators declared will support the `isAny` and `isNone` multi-selection operators. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item. +`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. A filter with no operators declared will support the `isAny` and `isNone` multi-selection operators by default. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded, and the filter won't allow selecting more than one item. Example: diff --git a/packages/dataviews/src/components/dataviews-view-config/index.tsx b/packages/dataviews/src/components/dataviews-view-config/index.tsx index 28e48525ffa73b..3146064a41922b 100644 --- a/packages/dataviews/src/components/dataviews-view-config/index.tsx +++ b/packages/dataviews/src/components/dataviews-view-config/index.tsx @@ -53,7 +53,11 @@ interface ViewTypeMenuProps { defaultLayouts?: SupportedLayouts; } -const DATAVIEWS_CONFIG_POPOVER_PROPS = { placement: 'bottom-end', offset: 9 }; +const DATAVIEWS_CONFIG_POPOVER_PROPS = { + className: 'dataviews-config__popover', + placement: 'bottom-end', + offset: 9, +}; function ViewTypeMenu( { defaultLayouts = { list: {}, grid: {}, table: {} }, @@ -619,6 +623,7 @@ function DataviewsViewConfigDropdown() { ); return ( ( - + diff --git a/packages/dataviews/src/components/dataviews-view-config/style.scss b/packages/dataviews/src/components/dataviews-view-config/style.scss index 8427796b6e15ed..0fd97b916b4aa8 100644 --- a/packages/dataviews/src/components/dataviews-view-config/style.scss +++ b/packages/dataviews/src/components/dataviews-view-config/style.scss @@ -6,6 +6,14 @@ line-height: $default-line-height; } +.dataviews-config__popover.is-expanded .dataviews-config__popover-content-wrapper { + overflow-y: scroll; + height: 100%; + .dataviews-view-config { + width: auto; + } +} + .dataviews-view-config__sort-direction .components-toggle-group-control-option-base { text-transform: uppercase; } diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index c230738a077d92..b0a2b3f7d76b81 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -548,7 +548,7 @@ function Layout( { return ( - +
    - ); - } -} diff --git a/packages/edit-site/src/components/error-boundary/test/error-boundary.js b/packages/edit-site/src/components/error-boundary/test/error-boundary.js deleted file mode 100644 index 27ec4c80419676..00000000000000 --- a/packages/edit-site/src/components/error-boundary/test/error-boundary.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * External dependencies - */ -import { render } from '@testing-library/react'; - -/** - * WordPress dependencies - */ -import * as wpHooks from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import ErrorBoundary from '../index'; - -const theError = new Error( 'Kaboom' ); - -const ChildComponent = () => { - throw theError; -}; - -describe( 'Error Boundary', () => { - describe( 'when error is thrown from a Child component', () => { - it( 'calls the `editor.ErrorBoundary.errorLogged` hook action with the error object', () => { - const doAction = jest.spyOn( wpHooks, 'doAction' ); - - render( - - - - ); - - expect( doAction ).toHaveBeenCalledWith( - 'editor.ErrorBoundary.errorLogged', - theError - ); - expect( console ).toHaveErrored(); - } ); - } ); -} ); diff --git a/packages/edit-site/src/components/error-boundary/warning.js b/packages/edit-site/src/components/error-boundary/warning.js deleted file mode 100644 index c4090c7e6b1190..00000000000000 --- a/packages/edit-site/src/components/error-boundary/warning.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; -import { Warning } from '@wordpress/block-editor'; -import { useCopyToClipboard } from '@wordpress/compose'; - -function CopyButton( { text, children } ) { - const ref = useCopyToClipboard( text ); - return ( - - ); -} - -export default function ErrorBoundaryWarning( { message, error } ) { - const actions = [ - - { __( 'Copy Error' ) } - , - ]; - - return ( - - { message } - - ); -} diff --git a/packages/edit-site/src/components/global-styles/preview-colors.js b/packages/edit-site/src/components/global-styles/preview-colors.js index 8c1008330ec068..a7e6563748ca03 100644 --- a/packages/edit-site/src/components/global-styles/preview-colors.js +++ b/packages/edit-site/src/components/global-styles/preview-colors.js @@ -10,7 +10,7 @@ import { * Internal dependencies */ import PresetColors from './preset-colors'; -import PreviewIframe from './preview-iframe'; +import PreviewWrapper from './preview-wrapper'; const firstFrameVariants = { start: { @@ -25,7 +25,7 @@ const firstFrameVariants = { const StylesPreviewColors = ( { label, isFocused, withHoverView } ) => { return ( - { ) } - + ); }; diff --git a/packages/edit-site/src/components/global-styles/preview-styles.js b/packages/edit-site/src/components/global-styles/preview-styles.js index dff1ef5a6f42ba..f392e99ae951e6 100644 --- a/packages/edit-site/src/components/global-styles/preview-styles.js +++ b/packages/edit-site/src/components/global-styles/preview-styles.js @@ -15,7 +15,7 @@ import { unlock } from '../../lock-unlock'; import { useStylesPreviewColors } from './hooks'; import TypographyExample from './typography-example'; import HighlightedColors from './highlighted-colors'; -import PreviewIframe from './preview-iframe'; +import PreviewWrapper from './preview-wrapper'; const { useGlobalStyle } = unlock( blockEditorPrivateApis ); @@ -67,7 +67,7 @@ const PreviewStyles = ( { label, isFocused, withHoverView, variation } ) => { const { paletteColors } = useStylesPreviewColors(); return ( - { ) } - + ); }; diff --git a/packages/edit-site/src/components/global-styles/preview-typography.js b/packages/edit-site/src/components/global-styles/preview-typography.js index 26ae13eaa09083..686ebd1e6f0656 100644 --- a/packages/edit-site/src/components/global-styles/preview-typography.js +++ b/packages/edit-site/src/components/global-styles/preview-typography.js @@ -7,11 +7,11 @@ import { __experimentalHStack as HStack } from '@wordpress/components'; * Internal dependencies */ import TypographyExample from './typography-example'; -import PreviewIframe from './preview-iframe'; +import PreviewWrapper from './preview-wrapper'; const StylesPreviewTypography = ( { variation, isFocused, withHoverView } ) => { return ( - { /> ) } - + ); }; diff --git a/packages/edit-site/src/components/global-styles/preview-iframe.js b/packages/edit-site/src/components/global-styles/preview-wrapper.js similarity index 73% rename from packages/edit-site/src/components/global-styles/preview-iframe.js rename to packages/edit-site/src/components/global-styles/preview-wrapper.js index e830accf6d939b..b3c83bad69d844 100644 --- a/packages/edit-site/src/components/global-styles/preview-iframe.js +++ b/packages/edit-site/src/components/global-styles/preview-wrapper.js @@ -1,27 +1,21 @@ /** * WordPress dependencies */ -import { - __unstableIframe as Iframe, - __unstableEditorStyles as EditorStyles, - privateApis as blockEditorPrivateApis, -} from '@wordpress/block-editor'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { __unstableMotion as motion } from '@wordpress/components'; import { useThrottle, useReducedMotion, useResizeObserver, } from '@wordpress/compose'; -import { useLayoutEffect, useState, useMemo } from '@wordpress/element'; +import { useLayoutEffect, useState } from '@wordpress/element'; /** * Internal dependencies */ import { unlock } from '../../lock-unlock'; -const { useGlobalStyle, useGlobalStylesOutput } = unlock( - blockEditorPrivateApis -); +const { useGlobalStyle } = unlock( blockEditorPrivateApis ); const normalizedWidth = 248; const normalizedHeight = 152; @@ -33,7 +27,7 @@ const THROTTLE_OPTIONS = { trailing: true, }; -export default function PreviewIframe( { +export default function PreviewWrapper( { children, label, isFocused, @@ -41,7 +35,6 @@ export default function PreviewIframe( { } ) { const [ backgroundColor = 'white' ] = useGlobalStyle( 'color.background' ); const [ gradientValue ] = useGlobalStyle( 'color.gradient' ); - const [ styles ] = useGlobalStylesOutput(); const disableMotion = useReducedMotion(); const [ isHovered, setIsHovered ] = useState( false ); const [ containerResizeListener, { width } ] = useResizeObserver(); @@ -54,7 +47,7 @@ export default function PreviewIframe( { THROTTLE_OPTIONS ); - // Must use useLayoutEffect to avoid a flash of the iframe at the wrong + // Must use useLayoutEffect to avoid a flash of the container at the wrong // size before the width is set. useLayoutEffect( () => { if ( width ) { @@ -62,7 +55,7 @@ export default function PreviewIframe( { } }, [ width, setThrottledWidth ] ); - // Must use useLayoutEffect to avoid a flash of the iframe at the wrong + // Must use useLayoutEffect to avoid a flash of the container at the wrong // size before the width is set. useLayoutEffect( () => { const newRatio = throttledWidth ? throttledWidth / normalizedWidth : 1; @@ -89,24 +82,6 @@ export default function PreviewIframe( { */ const ratio = ratioState ? ratioState : fallbackRatio; - /* - * Reset leaked styles from WP common.css and remove main content layout padding and border. - * Add pointer cursor to the body to indicate the iframe is interactive, - * similar to Typography variation previews. - */ - const editorStyles = useMemo( () => { - if ( styles ) { - return [ - ...styles, - { - css: 'html{overflow:hidden}body{min-width: 0;padding: 0;border: none;cursor: pointer;}', - isGlobalStyles: true, - }, - ]; - } - - return styles; - }, [ styles ] ); const isReady = !! width; return ( @@ -115,8 +90,8 @@ export default function PreviewIframe( { { containerResizeListener }
    { isReady && ( - +
    ) } ); diff --git a/packages/edit-site/src/components/global-styles/style.scss b/packages/edit-site/src/components/global-styles/style.scss index 4674b5e5fc3ca4..11dbae7bc66b19 100644 --- a/packages/edit-site/src/components/global-styles/style.scss +++ b/packages/edit-site/src/components/global-styles/style.scss @@ -6,7 +6,7 @@ cursor: pointer; } -.edit-site-global-styles-preview__iframe { +.edit-site-global-styles-preview__wrapper { max-width: 100%; display: block; width: 100%; @@ -230,10 +230,6 @@ // The &#{&} is a workaround for the specificity of the Card component. &#{&}, &#{&} .edit-site-global-styles-screen-root__active-style-tile-preview { - box-shadow: none; border-radius: $radius-small; - .block-editor-iframe__container { - border: 1px solid $gray-200; - } } } diff --git a/packages/edit-site/src/components/global-styles/typography-example.js b/packages/edit-site/src/components/global-styles/typography-example.js index a491ca57bf5be4..9c0a4e0e1cb13a 100644 --- a/packages/edit-site/src/components/global-styles/typography-example.js +++ b/packages/edit-site/src/components/global-styles/typography-example.js @@ -14,7 +14,9 @@ import { unlock } from '../../lock-unlock'; import { getFamilyPreviewStyle } from './font-library-modal/utils/preview-styles'; import { getFontFamilies } from './utils'; -const { GlobalStylesContext } = unlock( blockEditorPrivateApis ); +const { useGlobalStyle, GlobalStylesContext } = unlock( + blockEditorPrivateApis +); const { mergeBaseAndUserConfigs } = unlock( editorPrivateApis ); export default function PreviewTypography( { fontSize, variation } ) { @@ -23,6 +25,9 @@ export default function PreviewTypography( { fontSize, variation } ) { if ( variation ) { config = mergeBaseAndUserConfigs( base, variation ); } + + const [ textColor ] = useGlobalStyle( 'color.text' ); + const [ bodyFontFamilies, headingFontFamilies ] = getFontFamilies( config ); const bodyPreviewStyle = bodyFontFamilies ? getFamilyPreviewStyle( bodyFontFamilies ) @@ -31,6 +36,11 @@ export default function PreviewTypography( { fontSize, variation } ) { ? getFamilyPreviewStyle( headingFontFamilies ) : {}; + if ( textColor ) { + bodyPreviewStyle.color = textColor; + headingPreviewStyle.color = textColor; + } + if ( fontSize ) { bodyPreviewStyle.fontSize = fontSize; headingPreviewStyle.fontSize = fontSize; @@ -52,6 +62,7 @@ export default function PreviewTypography( { fontSize, variation } ) { } } style={ { textAlign: 'center', + lineHeight: 1, } } > diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index e7606c57a70484..00bc323ea1535c 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -25,6 +25,7 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { EditorSnackbars, UnsavedChangesWarning, + ErrorBoundary, privateApis as editorPrivateApis, } from '@wordpress/editor'; import { privateApis as coreCommandsPrivateApis } from '@wordpress/core-commands'; @@ -36,7 +37,6 @@ import { useDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import ErrorBoundary from '../error-boundary'; import { default as SiteHub, SiteHubMobile } from '../site-hub'; import ResizableFrame from '../resizable-frame'; import { unlock } from '../../lock-unlock'; @@ -136,7 +136,9 @@ function Layout() { } routeKey={ routeKey } > - { areas.sidebar } + + { areas.sidebar } + @@ -160,7 +162,7 @@ function Layout() { /> ) } - { areas.mobile } + { areas.mobile }
    ) } @@ -173,7 +175,7 @@ function Layout() { maxWidth: widths?.content, } } > - { areas.content } + { areas.content } ) } @@ -184,7 +186,7 @@ function Layout() { maxWidth: widths?.edit, } } > - { areas.edit } + { areas.edit } ) } diff --git a/packages/edit-site/src/components/sidebar-dataviews/index.js b/packages/edit-site/src/components/sidebar-dataviews/index.js index 410767650c6f36..312bf43d6df657 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/index.js +++ b/packages/edit-site/src/components/sidebar-dataviews/index.js @@ -26,7 +26,7 @@ export default function DataViewsSidebarContent( { postType } ) { return ( <> - + { defaultViews.map( ( dataview ) => { return ( {}; - export default function SidebarNavigationScreenGlobalStylesContent() { - const { storedSettings } = useSelect( ( select ) => { - const { getSettings } = unlock( select( editSiteStore ) ); - - return { - storedSettings: getSettings(), - }; - }, [] ); - const gap = 3; - // Wrap in a BlockEditorProvider to ensure that the Iframe's dependencies are - // loaded. This is necessary because the Iframe component waits until - // the block editor store's `__internalIsInitialized` is true before - // rendering the iframe. Without this, the iframe previews will not render - // in mobile viewport sizes, where the editor canvas is hidden. return ( - - - - - - - + + + + ); } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js index 5cf162a8cd28cc..bd40a5ad540d4c 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js @@ -18,7 +18,7 @@ import { store as editSiteStore } from '../../store'; export function MainSidebarNavigationContent() { return ( - + - + { navigationMenus?.map( ( { id, title, status }, index ) => ( + ; innerBlocks?: Block[]; diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index 4269cdb4bc90d1..9f98c2c172e091 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -21,9 +21,11 @@ @import "./components/sidebar-navigation-screen-details-footer/style.scss"; @import "./components/sidebar-navigation-screen-navigation-menu/style.scss"; @import "./components/sidebar-navigation-screen-patterns/style.scss"; +@import "./components/sidebar-navigation-screen-navigation-menus/style.scss"; +@import "./components/sidebar-navigation-screen-main/style.scss"; +@import "./components/sidebar-navigation-screen-templates-browse/style.scss"; @import "./components/sidebar-dataviews/style.scss"; @import "./components/site-hub/style.scss"; -@import "./components/sidebar-navigation-screen-navigation-menus/style.scss"; @import "./components/site-icon/style.scss"; @import "./components/style-book/style.scss"; @import "./components/editor-canvas-container/style.scss"; diff --git a/packages/editor/src/components/error-boundary/index.js b/packages/editor/src/components/error-boundary/index.js index d43af5a556b67b..f3e6e5fd127afc 100644 --- a/packages/editor/src/components/error-boundary/index.js +++ b/packages/editor/src/components/error-boundary/index.js @@ -3,9 +3,12 @@ */ import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; +import { + Button, + __experimentalHStack as HStack, + __experimentalText as Text, +} from '@wordpress/components'; import { select } from '@wordpress/data'; -import { Warning } from '@wordpress/block-editor'; import { useCopyToClipboard } from '@wordpress/compose'; import { doAction } from '@wordpress/hooks'; @@ -26,10 +29,10 @@ function getContent() { } catch ( error ) {} } -function CopyButton( { text, children } ) { +function CopyButton( { text, children, variant = 'secondary' } ) { const ref = useCopyToClipboard( text ); return ( - ); @@ -54,23 +57,34 @@ class ErrorBoundary extends Component { render() { const { error } = this.state; + const { canCopyContent = false } = this.props; if ( ! error ) { return this.props.children; } - const actions = [ - - { __( 'Copy Post Text' ) } - , - - { __( 'Copy Error' ) } - , - ]; - return ( - - { __( 'The editor has encountered an unexpected error.' ) } - + + + { __( 'The editor has encountered an unexpected error.' ) } + + + { canCopyContent && ( + + { __( 'Copy contents' ) } + + ) } + + { __( 'Copy error' ) } + + + ); } } diff --git a/packages/editor/src/components/error-boundary/style.scss b/packages/editor/src/components/error-boundary/style.scss index 2913a644b94576..4d59c511667088 100644 --- a/packages/editor/src/components/error-boundary/style.scss +++ b/packages/editor/src/components/error-boundary/style.scss @@ -1,7 +1,11 @@ .editor-error-boundary { + font-family: $default-font; margin: auto; max-width: 780px; - padding: 20px; + padding: 1em; margin-top: 60px; box-shadow: $elevation-large; + border: $border-width solid $gray-900; + border-radius: $radius-small; + background-color: $white; } diff --git a/packages/editor/src/components/post-publish-panel/prepublish.js b/packages/editor/src/components/post-publish-panel/prepublish.js index 193960b9cc8345..e8d5c69b769302 100644 --- a/packages/editor/src/components/post-publish-panel/prepublish.js +++ b/packages/editor/src/components/post-publish-panel/prepublish.js @@ -75,7 +75,7 @@ function PostPublishPanelPrepublish( { children } ) { if ( ! hasPublishAction ) { prePublishTitle = __( 'Are you ready to submit for review?' ); prePublishBodyText = __( - 'When you’re ready, submit your work for review, and an Editor will be able to approve it for you.' + 'Your work will be reviewed and then approved.' ); } else if ( isBeingScheduled ) { prePublishTitle = __( 'Are you ready to schedule?' ); diff --git a/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap index b074159ac423d4..9fb3d24cd2931f 100644 --- a/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap @@ -466,7 +466,7 @@ exports[`PostPublishPanel should render the pre-publish panel if post status is

    - When you’re ready, submit your work for review, and an Editor will be able to approve it for you. + Your work will be reviewed and then approved.

    - When you’re ready, submit your work for review, and an Editor will be able to approve it for you. + Your work will be reviewed and then approved.

    - // @ts-ignore + // @ts-expect-error getPostType is not typed with 'wp_template_part' as argument. select( coreStore ).getPostType( 'wp_template_part' )?.labels ?.add_new_item, [] @@ -77,7 +77,6 @@ export default function CreateTemplatePartModal( { focusOnMount="firstContentElement" size="medium" > - { /* @ts-ignore */ } ); diff --git a/storybook/stories/foundations/design-language/elevation.mdx b/storybook/stories/foundations/design-language/elevation.mdx index 11591568b3600c..fcacfd2ee5a8d6 100644 --- a/storybook/stories/foundations/design-language/elevation.mdx +++ b/storybook/stories/foundations/design-language/elevation.mdx @@ -1,4 +1,4 @@ -import { Meta, Typeset } from '@storybook/addon-docs/blocks'; +import { Meta, Typeset } from '@storybook/blocks'; import elevation from './static/elevation.svg'; import elevationExamples from './static/elevation-examples.svg'; diff --git a/storybook/stories/foundations/design-language/radius.mdx b/storybook/stories/foundations/design-language/radius.mdx index 9117bf940d45a2..2cf5b05a92efee 100644 --- a/storybook/stories/foundations/design-language/radius.mdx +++ b/storybook/stories/foundations/design-language/radius.mdx @@ -1,4 +1,4 @@ -import { Meta, Typeset } from '@storybook/addon-docs/blocks'; +import { Meta, Typeset } from '@storybook/blocks'; import radius from './static/radius.svg'; import radiusDo from './static/radius-do.svg'; import radiusDont from './static/radius-dont.svg'; @@ -31,25 +31,29 @@ These steps are defined as tokens. To view the values and understand how to use - Accessibility: Larger radius values can create a more approachable and friendly appearance, especially for larger components like cards and modals that demand more attention from users. - - - - - - - - + + + + + + + + + + + +
    ✅ Do🚫 Don't
    - Radius do - - - Scale application of radius with element or container size. - - - Use `radius-round` for circles and `radius-full` for pills. - - Radius don't - - - Don't nest larger radii inside smaller radii. - - - Don't apply the same - radius value to container and immediate descendent. -
    ✅ Do🚫 Don't
    + Radius do + + - Scale application of radius with element or container size. + + - Use `radius-round` for circles and `radius-full` for pills. + + Radius don't + + - Don't nest larger radii inside smaller radii. + + - Don't apply the same + radius value to container and immediate descendent. +
    diff --git a/storybook/stories/tokens/elevation.mdx b/storybook/stories/tokens/elevation.mdx index 5afb7e75266c4a..b3b0cd1a881b40 100644 --- a/storybook/stories/tokens/elevation.mdx +++ b/storybook/stories/tokens/elevation.mdx @@ -1,4 +1,4 @@ -import { Meta } from '@storybook/addon-docs/blocks'; +import { Meta } from '@storybook/blocks'; import { TokensTable } from './components.tsx'; diff --git a/storybook/stories/tokens/typography.mdx b/storybook/stories/tokens/typography.mdx index c3b49b2865f186..9d0e4aedf8c017 100644 --- a/storybook/stories/tokens/typography.mdx +++ b/storybook/stories/tokens/typography.mdx @@ -1,4 +1,4 @@ -import { Meta, Typeset } from '@storybook/addon-docs/blocks'; +import { Meta, Typeset } from '@storybook/blocks'; diff --git a/test/e2e/specs/editor/blocks/columns.spec.js b/test/e2e/specs/editor/blocks/columns.spec.js index eea6e321aacb11..29262aef810d21 100644 --- a/test/e2e/specs/editor/blocks/columns.spec.js +++ b/test/e2e/specs/editor/blocks/columns.spec.js @@ -63,7 +63,7 @@ test.describe( 'Columns', () => { ); await editor.clickBlockToolbarButton( 'Options' ); await page.click( 'role=menuitem[name="Lock"i]' ); - await page.locator( 'role=checkbox[name="Prevent removal"i]' ).check(); + await page.locator( 'role=checkbox[name="Lock removal"i]' ).check(); await page.click( 'role=button[name="Apply"i]' ); // Select columns block diff --git a/test/e2e/specs/editor/various/block-locking.spec.js b/test/e2e/specs/editor/various/block-locking.spec.js index eafb468902ef92..a8895d282fb956 100644 --- a/test/e2e/specs/editor/various/block-locking.spec.js +++ b/test/e2e/specs/editor/various/block-locking.spec.js @@ -16,7 +16,7 @@ test.describe( 'Block Locking', () => { await editor.clickBlockOptionsMenuItem( 'Lock' ); - await page.click( 'role=checkbox[name="Prevent removal"]' ); + await page.click( 'role=checkbox[name="Lock removal"]' ); await page.click( 'role=button[name="Apply"]' ); await expect( @@ -35,7 +35,7 @@ test.describe( 'Block Locking', () => { await editor.clickBlockOptionsMenuItem( 'Lock' ); - await page.click( 'role=checkbox[name="Disable movement"]' ); + await page.click( 'role=checkbox[name="Lock movement"]' ); await page.click( 'role=button[name="Apply"]' ); // Hide options. diff --git a/test/e2e/specs/editor/various/block-switcher.spec.js b/test/e2e/specs/editor/various/block-switcher.spec.js index 41b0b1ee49c88c..cb95c4d395c664 100644 --- a/test/e2e/specs/editor/various/block-switcher.spec.js +++ b/test/e2e/specs/editor/various/block-switcher.spec.js @@ -107,7 +107,7 @@ test.describe( 'Block Switcher', () => { await expect( button ).toBeEnabled(); await editor.clickBlockOptionsMenuItem( 'Lock' ); - await page.click( 'role=checkbox[name="Prevent removal"]' ); + await page.click( 'role=checkbox[name="Lock removal"]' ); await page.click( 'role=button[name="Apply"]' ); // Verify the block switcher isn't enabled. diff --git a/test/e2e/specs/site-editor/preload.spec.js b/test/e2e/specs/site-editor/preload.spec.js index 2cd61283fbd9e8..e731e932e30523 100644 --- a/test/e2e/specs/site-editor/preload.spec.js +++ b/test/e2e/specs/site-editor/preload.spec.js @@ -6,6 +6,7 @@ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); test.describe( 'Preload', () => { test.beforeAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'emptytheme' ); + await requestUtils.resetPreferences(); } ); test.afterAll( async ( { requestUtils } ) => { diff --git a/test/e2e/specs/site-editor/site-editor-inserter.spec.js b/test/e2e/specs/site-editor/site-editor-inserter.spec.js index 04075cbedab308..a730367d841bf8 100644 --- a/test/e2e/specs/site-editor/site-editor-inserter.spec.js +++ b/test/e2e/specs/site-editor/site-editor-inserter.spec.js @@ -5,8 +5,9 @@ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); test.describe( 'Site Editor Inserter', () => { test.beforeAll( async ( { requestUtils } ) => { + // We need the theme to have a section root so zoom out is enabled await Promise.all( [ - requestUtils.activateTheme( 'emptytheme' ), + requestUtils.activateTheme( 'twentytwentyfour' ), requestUtils.deleteAllTemplates( 'wp_template' ), requestUtils.deleteAllTemplates( 'wp_template_part' ), ] ); @@ -21,47 +22,303 @@ test.describe( 'Site Editor Inserter', () => { await editor.canvas.locator( 'body' ).click(); } ); + test.use( { + InserterUtils: async ( { editor, page }, use ) => { + await use( new InserterUtils( { editor, page } ) ); + }, + } ); + + // eslint-disable-next-line playwright/expect-expect test( 'inserter toggle button should toggle global inserter', async ( { - page, + InserterUtils, } ) => { - await page.click( 'role=button[name="Block Inserter"i]' ); - - // Visibility check - await expect( - page.locator( 'role=searchbox[name="Search"i]' ) - ).toBeVisible(); - await page.click( 'role=button[name="Block Inserter"i]' ); - //Hidden State check - await expect( - page.locator( 'role=searchbox[name="Search"i]' ) - ).toBeHidden(); + await InserterUtils.openBlockLibrary(); + await InserterUtils.closeBlockLibrary(); } ); // A test for https://github.com/WordPress/gutenberg/issues/43090. test( 'should close the inserter when clicking on the toggle button', async ( { - page, editor, + InserterUtils, } ) => { - const inserterButton = page.getByRole( 'button', { - name: 'Block Inserter', - exact: true, - } ); - const blockLibrary = page.getByRole( 'region', { - name: 'Block Library', - } ); - const beforeBlocks = await editor.getBlocks(); - await inserterButton.click(); - await blockLibrary.getByRole( 'tab', { name: 'Blocks' } ).click(); - await blockLibrary.getByRole( 'option', { name: 'Buttons' } ).click(); + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await InserterUtils.blockLibrary + .getByRole( 'option', { name: 'Buttons' } ) + .click(); await expect .poll( editor.getBlocks ) .toMatchObject( [ ...beforeBlocks, { name: 'core/buttons' } ] ); - await inserterButton.click(); + await InserterUtils.closeBlockLibrary(); + } ); + + test.describe( 'Inserter Zoom Level UX', () => { + test.use( { + ZoomUtils: async ( { editor, page }, use ) => { + await use( new ZoomUtils( { editor, page } ) ); + }, + } ); + + test( 'should intialize correct active tab based on zoom level', async ( { + InserterUtils, + ZoomUtils, + } ) => { + await test.step( 'should open the inserter to blocks tab from default zoom level', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + + // Zoom canvas should not be active + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom canvas should not be active + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should open the inserter to patterns tab if zoomed out', async () => { + await ZoomUtils.enterZoomOut(); + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Patterns' ); + + // Zoom canvas should still be active + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + await InserterUtils.closeBlockLibrary(); + + // We should still be in Zoom Out + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + } ); + } ); + + test( 'should set the correct zoom level when changing tabs', async ( { + InserterUtils, + ZoomUtils, + } ) => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await test.step( 'should zoom out when activating patterns tab', async () => { + await InserterUtils.activateTab( 'Patterns' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + } ); + + await test.step( 'should reset zoom level when activating blocks tab', async () => { + await InserterUtils.activateTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should zoom out when activating media tab', async () => { + await InserterUtils.activateTab( 'Media' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + } ); + } ); + + test( 'should reset the zoom level when closing the inserter if we most recently changed the zoom level', async ( { + InserterUtils, + ZoomUtils, + } ) => { + await test.step( 'should reset zoom when closing from patterns tab', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Patterns' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom Level should be reset + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should preserve default zoom level when closing from blocks tab', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Media' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + await InserterUtils.activateTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom Level should stay at default level + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should preserve default zoom level when closing from blocks tab even if user manually toggled zoom level on previous tab', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Media' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + // Toggle zoom level manually + await ZoomUtils.exitZoomOut(); + + await InserterUtils.activateTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom Level should stay at default level + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); - await expect( blockLibrary ).toBeHidden(); + await test.step( 'should preserve default zoom level when closing from blocks tab even if user manually toggled zoom level on previous tab twice', async () => { + // Open inserter + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Media' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + // Toggle zoom level manually twice + await ZoomUtils.exitZoomOut(); + await ZoomUtils.enterZoomOut(); + + await InserterUtils.activateTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom Level should stay at default level + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + } ); + + test( 'should keep the zoom level manually set by the user if the user most recently set the zoom level', async ( { + InserterUtils, + ZoomUtils, + } ) => { + await test.step( 'should respect manual zoom level set when closing from patterns tab', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Patterns' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + await ZoomUtils.exitZoomOut(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom Level should stay reset + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should respect manual zoom level set when closing from patterns tab when toggled twice', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Patterns' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + await ZoomUtils.exitZoomOut(); + + await ZoomUtils.enterZoomOut(); + + await InserterUtils.closeBlockLibrary(); + + // Should stay zoomed out since it was manually engaged + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + // Reset test + await ZoomUtils.exitZoomOut(); + } ); + + await test.step( 'should not reset zoom level if zoom level manually changed from blocks tab', async () => { + await InserterUtils.openBlockLibrary(); + await expect( InserterUtils.blockLibrary ).toBeVisible(); + + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await ZoomUtils.enterZoomOut(); + + await InserterUtils.closeBlockLibrary(); + + // Should stay zoomed out since it was manually engaged + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + } ); + } ); } ); } ); + +class InserterUtils { + constructor( { editor, page } ) { + this.editor = editor; + this.page = page; + this.blockLibrary = this.page.getByRole( 'region', { + name: 'Block Library', + } ); + this.inserterButton = this.page.getByRole( 'button', { + name: 'Block Inserter', + exact: true, + } ); + } + + // Manually naming as open and close these makes it clearer when reading + // through the test instead of using a toggle method with a boolean + async openBlockLibrary() { + await this.inserterButton.click(); + await expect( this.blockLibrary ).toBeVisible(); + } + + async closeBlockLibrary() { + await this.inserterButton.click(); + await expect( this.blockLibrary ).toBeHidden(); + } + + getBlockLibraryTab( name ) { + return this.page.getByRole( 'tab', { name } ); + } + + async expectActiveTab( name ) { + await expect( this.getBlockLibraryTab( name ) ).toHaveAttribute( + 'aria-selected', + 'true' + ); + } + + async activateTab( name ) { + await this.getBlockLibraryTab( name ).click(); + // For brevity, adding this check here. It should always be done after the tab is clicked + await this.expectActiveTab( name ); + } +} + +class ZoomUtils { + constructor( { editor, page } ) { + this.editor = editor; + this.page = page; + this.zoomCanvas = this.page.locator( '.is-zoomed-out' ); + this.zoomButton = this.page.getByRole( 'button', { + name: 'Zoom Out', + exact: true, + } ); + } + + // Manually naming as enter and exit these makes it clearer when reading + // through the test instead of using a toggle method with a boolean + async enterZoomOut() { + await this.zoomButton.click(); + await expect( this.zoomCanvas ).toBeVisible(); + } + + async exitZoomOut() { + await this.zoomButton.click(); + await expect( this.zoomCanvas ).toBeHidden(); + } +}