diff --git a/packages/block-editor/src/components/inserter/category-tabs/index.js b/packages/block-editor/src/components/inserter/category-tabs/index.js
index 6a02cf1a170c45..6d8f5fcbca1334 100644
--- a/packages/block-editor/src/components/inserter/category-tabs/index.js
+++ b/packages/block-editor/src/components/inserter/category-tabs/index.js
@@ -2,14 +2,10 @@
* WordPress dependencies
*/
import { usePrevious, useReducedMotion } from '@wordpress/compose';
-import { isRTL } from '@wordpress/i18n';
import {
- __experimentalHStack as HStack,
- FlexBlock,
privateApis as componentsPrivateApis,
__unstableMotion as motion,
} from '@wordpress/components';
-import { Icon, chevronRight, chevronLeft } from '@wordpress/icons';
/**
* Internal dependencies
@@ -55,18 +51,12 @@ function CategoryTabs( {
-
- { category.label }
-
-
+ { category.label }
) ) }
diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss
index f3fa8d1e7df04b..9e727b13795249 100644
--- a/packages/block-editor/src/components/inserter/style.scss
+++ b/packages/block-editor/src/components/inserter/style.scss
@@ -214,55 +214,15 @@ $block-inserter-tabs-height: 44px;
.block-editor-inserter__media-tabs-container,
.block-editor-inserter__block-patterns-tabs-container {
+ flex-grow: 1;
padding: $grid-unit-20;
- height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.block-editor-inserter__category-tablist {
- display: flex;
- flex-direction: column;
- border: none;
margin-bottom: $grid-unit-10;
- // Push the listitem wrapping the "explore" button to the bottom of the panel.
- div[role="listitem"]:last-child {
- margin-top: auto;
- }
-
- // Temporarily disable the component's indicator animation.
- // TODO: remove in favor of using the native component's styles and behavior,
- // see https://github.com/WordPress/gutenberg/pull/62879#issuecomment-2219720582
- &[aria-orientation="vertical"]::after {
- content: none;
- }
-
- .block-editor-inserter__category-tab {
- // Account for the icon on the right so that it's visually balanced.
- padding: $grid-unit-10 $grid-unit-05 $grid-unit-10 $grid-unit-15;
- text-align: left;
- font-weight: inherit;
- display: block;
- position: relative;
- height: auto;
-
- &[aria-selected="true"] {
- color: var(--wp-admin-theme-color);
-
- .components-flex-item {
- filter: brightness(0.95);
- }
-
- svg {
- fill: var(--wp-admin-theme-color);
- }
- }
-
- &::before {
- display: none;
- }
- }
}
.block-editor-inserter__category-panel {
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 0bf9776d01a5de..4ae124347187e7 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -8,6 +8,10 @@
- `RangeControl`: do not tooltip contents to the DOM when not shown ([#65875](https://github.com/WordPress/gutenberg/pull/65875)).
- `Tabs`: fix skipping indication animation glitch ([#65878](https://github.com/WordPress/gutenberg/pull/65878)).
+### Enhancements
+
+- `Tabs`: revamped vertical orientation styles ([#65387](https://github.com/WordPress/gutenberg/pull/65387)).
+
## 28.9.0 (2024-10-03)
### Bug Fixes
diff --git a/packages/components/src/tabs/styles.ts b/packages/components/src/tabs/styles.ts
index 4f6b4a4c7c8dcb..926abc3e34b102 100644
--- a/packages/components/src/tabs/styles.ts
+++ b/packages/components/src/tabs/styles.ts
@@ -9,18 +9,15 @@ import * as Ariakit from '@ariakit/react';
*/
import { COLORS, CONFIG } from '../utils';
import { space } from '../utils/space';
+import Icon from '../icon';
export const TabListWrapper = styled.div`
- position: relative;
display: flex;
align-items: stretch;
- flex-direction: row;
- text-align: center;
overflow-x: auto;
&[aria-orientation='vertical'] {
flex-direction: column;
- text-align: start;
}
:where( [aria-orientation='horizontal'] ) {
@@ -40,11 +37,12 @@ export const TabListWrapper = styled.div`
@media not ( prefers-reduced-motion ) {
&[data-indicator-animated]::before {
- transition-property: transform;
+ transition-property: transform, border-radius, border-block;
transition-duration: 0.2s;
transition-timing-function: ease-out;
}
}
+ position: relative;
&::before {
content: '';
position: absolute;
@@ -59,7 +57,7 @@ export const TabListWrapper = styled.div`
/* Using a large value to avoid antialiasing rounding issues
when scaling in the transform, see: https://stackoverflow.com/a/52159123 */
--antialiasing-factor: 100;
- &:not( [aria-orientation='vertical'] ) {
+ &[aria-orientation='horizontal'] {
--fade-width: 4rem;
--fade-gradient-base: transparent 0%, black var( --fade-width );
--fade-gradient-composed: var( --fade-gradient-base ), black 60%,
@@ -104,40 +102,67 @@ export const TabListWrapper = styled.div`
${ COLORS.theme.accent };
}
}
- &[aria-orientation='vertical']::before {
- top: 0;
- left: 0;
- width: 100%;
- height: calc( var( --antialiasing-factor ) * 1px );
- transform: translateY( calc( var( --selected-top, 0 ) * 1px ) )
- scaleY(
+ &[aria-orientation='vertical'] {
+ &::before {
+ /* Adjusting the border radius to match the scaling in the y axis. */
+ border-radius: ${ CONFIG.radiusSmall } /
calc(
- var( --selected-height, 0 ) / var( --antialiasing-factor )
- )
+ ${ CONFIG.radiusSmall } /
+ (
+ var( --selected-height, 0 ) /
+ var( --antialiasing-factor )
+ )
+ );
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: calc( var( --antialiasing-factor ) * 1px );
+ transform: translateY( calc( var( --selected-top, 0 ) * 1px ) )
+ scaleY(
+ calc(
+ var( --selected-height, 0 ) /
+ var( --antialiasing-factor )
+ )
+ );
+ background-color: color-mix(
+ in srgb,
+ ${ COLORS.theme.accent },
+ transparent 96%
);
- background-color: ${ COLORS.theme.gray[ 100 ] };
+ }
+ &[data-select-on-move='true']:has(
+ :is( :focus-visible, [data-focus-visible] )
+ )::before {
+ box-sizing: border-box;
+ border: var( --wp-admin-border-width-focus ) solid
+ ${ COLORS.theme.accent };
+ /* Adjusting the border width to match the scaling in the y axis. */
+ border-block-width: calc(
+ var( --wp-admin-border-width-focus, 1px ) /
+ (
+ var( --selected-height, 0 ) /
+ var( --antialiasing-factor )
+ )
+ );
+ }
}
`;
export const Tab = styled( Ariakit.Tab )`
& {
- scroll-margin: 24px;
- flex-grow: 1;
- flex-shrink: 0;
- display: inline-flex;
- align-items: center;
- position: relative;
+ /* Resets */
border-radius: 0;
- height: ${ space( 12 ) };
background: transparent;
border: none;
box-shadow: none;
+
+ flex: 1 0 auto;
+ white-space: nowrap;
+ display: flex;
+ align-items: center;
cursor: pointer;
- line-height: 1.2; // Some languages characters e.g. Japanese may have a native higher line-height.
- padding: ${ space( 4 ) };
- margin-left: 0;
- font-weight: 500;
- text-align: inherit;
+ line-height: 1.2; // Characters in some languages (e.g. Japanese) may have a native higher line-height.
+ font-weight: 400;
color: ${ COLORS.theme.foreground };
&[aria-disabled='true'] {
@@ -145,24 +170,19 @@ export const Tab = styled( Ariakit.Tab )`
color: ${ COLORS.ui.textDisabled };
}
- &:not( [aria-disabled='true'] ):hover {
+ &:not( [aria-disabled='true'] ):is( :hover, [data-focus-visible] ) {
color: ${ COLORS.theme.accent };
}
&:focus:not( :disabled ) {
- position: relative;
box-shadow: none;
outline: none;
}
- // Focus.
+ // Focus indicator.
+ position: relative;
&::after {
- content: '';
position: absolute;
- top: ${ space( 3 ) };
- right: ${ space( 3 ) };
- bottom: ${ space( 3 ) };
- left: ${ space( 3 ) };
pointer-events: none;
// Draw the indicator.
@@ -175,23 +195,69 @@ export const Tab = styled( Ariakit.Tab )`
opacity: 0;
@media not ( prefers-reduced-motion ) {
- transition: opacity 0.1s linear;
+ transition: opacity 0.15s 0.15s linear;
}
}
- &:focus-visible::after {
+ &[data-focus-visible]::after {
opacity: 1;
}
}
+ [aria-orientation='horizontal'] & {
+ padding-inline: ${ space( 4 ) };
+ height: ${ space( 12 ) };
+ text-align: center;
+ scroll-margin: 24px;
+
+ &::after {
+ content: '';
+ inset: ${ space( 3 ) };
+ }
+ }
+
[aria-orientation='vertical'] & {
- min-height: ${ space(
- 10
- ) }; // Avoid fixed height to allow for long strings that go in multiple lines.
+ padding: ${ space( 2 ) } ${ space( 3 ) };
+ min-height: ${ space( 10 ) };
+ text-align: start;
+
+ &[aria-selected='true'] {
+ color: ${ COLORS.theme.accent };
+ fill: currentColor;
+ }
+ }
+ [aria-orientation='vertical'][data-select-on-move='false'] &::after {
+ content: '';
+ inset: var( --wp-admin-border-width-focus );
}
+`;
+
+export const TabChildren = styled.span`
+ flex-grow: 1;
+`;
+export const TabChevron = styled( Icon )`
+ flex-shrink: 0;
+ margin-inline-end: ${ space( -1 ) };
[aria-orientation='horizontal'] & {
- justify-content: center;
+ display: none;
+ }
+ opacity: 0;
+ [role='tab']:is( [aria-selected='true'], [data-focus-visible], :hover ) & {
+ opacity: 1;
+ }
+ // The chevron is transitioned into existence when selectOnMove is enabled,
+ // because otherwise it looks jarring, as it shows up outside of the focus
+ // indicator that's being animated at the same time.
+ @media not ( prefers-reduced-motion ) {
+ [data-select-on-move='true']
+ [role='tab']:is( [aria-selected='true'], )
+ & {
+ transition: opacity 0.3s ease-in;
+ }
+ }
+ &:dir( rtl ) {
+ rotate: 180deg;
}
`;
@@ -201,7 +267,7 @@ export const TabPanel = styled( Ariakit.TabPanel )`
outline: none;
}
- &:focus-visible {
+ &[data-focus-visible] {
box-shadow: 0 0 0 var( --wp-admin-border-width-focus )
${ COLORS.theme.accent };
// Windows high contrast mode.
diff --git a/packages/components/src/tabs/tab.tsx b/packages/components/src/tabs/tab.tsx
index e1aa85c636cdd1..29f6111adc8397 100644
--- a/packages/components/src/tabs/tab.tsx
+++ b/packages/components/src/tabs/tab.tsx
@@ -10,8 +10,13 @@ import { forwardRef } from '@wordpress/element';
import type { TabProps } from './types';
import warning from '@wordpress/warning';
import { useTabsContext } from './context';
-import { Tab as StyledTab } from './styles';
+import {
+ Tab as StyledTab,
+ TabChildren as StyledTabChildren,
+ TabChevron as StyledTabChevron,
+} from './styles';
import type { WordPressComponentProps } from '../context';
+import { chevronRight } from '@wordpress/icons';
export const Tab = forwardRef<
HTMLButtonElement,
@@ -33,7 +38,8 @@ export const Tab = forwardRef<
render={ render }
{ ...otherProps }
>
- { children }
+ { children }
+
);
} );
diff --git a/packages/components/src/tabs/tablist.tsx b/packages/components/src/tabs/tablist.tsx
index a861d3294aae66..512a3eb6724289 100644
--- a/packages/components/src/tabs/tablist.tsx
+++ b/packages/components/src/tabs/tablist.tsx
@@ -115,6 +115,7 @@ export const TabList = forwardRef<
render={ }
onBlur={ onBlur }
tabIndex={ -1 }
+ data-select-on-move={ selectOnMove ? 'true' : 'false' }
{ ...otherProps }
className={ clsx(
overflow.first && 'is-overflowing-first',
diff --git a/test/e2e/specs/editor/various/a11y.spec.js b/test/e2e/specs/editor/various/a11y.spec.js
index 3ec7318ab89e78..8f63b57fda657f 100644
--- a/test/e2e/specs/editor/various/a11y.spec.js
+++ b/test/e2e/specs/editor/various/a11y.spec.js
@@ -123,7 +123,14 @@ test.describe( 'a11y (@firefox, @webkit)', () => {
test( 'should make the modal content focusable when it is scrollable', async ( {
page,
pageUtils,
+ browserName,
} ) => {
+ // eslint-disable-next-line playwright/no-skipped-test
+ test.skip(
+ browserName === 'webkit',
+ 'Known bug with focus order in Safari.'
+ );
+
// Note: this test depends on a particular viewport height to determine whether or not
// the modal content is scrollable. If this tests fails and needs to be debugged locally,
// double-check the viewport height when running locally versus in CI. Additionally,