Skip to content

Commit 5a59680

Browse files
DaniGuardiolakarthick-murugan
authored andcommitted
Tabs: unify vertical tabs styles (WordPress#65387)
* Remove inserter pattern overrides * Make font weights 400 (inactive) and 500 (active) * Apply styles only when vertical. * Make vertical indicator theme accent color at 4% opacity. * Make height 48px. * Add radius. * Also use hover styles in focus-visible. * Fix indicator not visible in inserter > patterns/media. * Adjust padding. * Tweak focus ring. * Wrap long labels. * Add chevron and fix a few minor details. * Fix merge issues. * Fix focus indicator (gets cropped with the new overflow auto setting) * Fix unwanted chevron. * Fix unwanted nested scrollbar in inserter > patterns/media vertical tabs. * Switch to transform for performance. * Adjust border-radius based on scaling factor. Co-authored-by: DaniGuardiola <[email protected]> Co-authored-by: ciampo <[email protected]> Co-authored-by: stokesman <[email protected]> Co-authored-by: jameskoster <[email protected]> Co-authored-by: jasmussen <[email protected]> Co-authored-by: t-hamano <[email protected]> Co-authored-by: ndiego <[email protected]> Co-authored-by: jeryj <[email protected]> * Apply feedback. * Add changelog entry. * Switch to `padding-inline`. * Remove unnecessary styles. * Fix horizontal tabs height. * Remove more unnecessary styles (padding). * Make horizontal padding specific to inline. * Make flex/whitespace styles more explicit. * Make scroll margin specific to vertical tabs. * The "inline" in inline-flex is unnecessary and confusing, removed it. * Remove unnecessary position: relative * Make resets more explicit * Remove unnecessary text-align. * Improve comment * Remove unnecessary margin-left * Clean up TabList styles. * Adjust text-align. * Clean up selector * Fix focus indicator * Clean up position: relative. * Fix typo. * Add position: relative back. * Improve focus indicator when selectOnMove is enabled. * Add fade in effect to chevron when selectOnMove is enabled. * Use [data-focus-visible] consistently. * Styles clean up. * Add comment for clarity. * Move scroll-margin to the right place. * Use CSS variable for accuracy. * Fix overflow. * Skip failing test for Safari :( * Fix flashing issue. * Transition chevron only on selected and not on hover or focus-visible. * Improve chevron opacity transition with suggested value. * fix changelog
1 parent 69a803e commit 5a59680

File tree

7 files changed

+131
-97
lines changed

7 files changed

+131
-97
lines changed

packages/block-editor/src/components/inserter/category-tabs/index.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@
22
* WordPress dependencies
33
*/
44
import { usePrevious, useReducedMotion } from '@wordpress/compose';
5-
import { isRTL } from '@wordpress/i18n';
65
import {
7-
__experimentalHStack as HStack,
8-
FlexBlock,
96
privateApis as componentsPrivateApis,
107
__unstableMotion as motion,
118
} from '@wordpress/components';
12-
import { Icon, chevronRight, chevronLeft } from '@wordpress/icons';
139

1410
/**
1511
* Internal dependencies
@@ -55,18 +51,12 @@ function CategoryTabs( {
5551
<Tabs.Tab
5652
key={ category.name }
5753
tabId={ category.name }
58-
className="block-editor-inserter__category-tab"
5954
aria-label={ category.label }
6055
aria-current={
6156
category === selectedCategory ? 'true' : undefined
6257
}
6358
>
64-
<HStack>
65-
<FlexBlock>{ category.label }</FlexBlock>
66-
<Icon
67-
icon={ isRTL() ? chevronLeft : chevronRight }
68-
/>
69-
</HStack>
59+
{ category.label }
7060
</Tabs.Tab>
7161
) ) }
7262
</Tabs.TabList>

packages/block-editor/src/components/inserter/style.scss

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -214,55 +214,15 @@ $block-inserter-tabs-height: 44px;
214214

215215
.block-editor-inserter__media-tabs-container,
216216
.block-editor-inserter__block-patterns-tabs-container {
217+
flex-grow: 1;
217218
padding: $grid-unit-20;
218-
height: 100%;
219219
display: flex;
220220
flex-direction: column;
221221
justify-content: space-between;
222222
}
223223

224224
.block-editor-inserter__category-tablist {
225-
display: flex;
226-
flex-direction: column;
227-
border: none;
228225
margin-bottom: $grid-unit-10;
229-
// Push the listitem wrapping the "explore" button to the bottom of the panel.
230-
div[role="listitem"]:last-child {
231-
margin-top: auto;
232-
}
233-
234-
// Temporarily disable the component's indicator animation.
235-
// TODO: remove in favor of using the native component's styles and behavior,
236-
// see https://github.com/WordPress/gutenberg/pull/62879#issuecomment-2219720582
237-
&[aria-orientation="vertical"]::after {
238-
content: none;
239-
}
240-
241-
.block-editor-inserter__category-tab {
242-
// Account for the icon on the right so that it's visually balanced.
243-
padding: $grid-unit-10 $grid-unit-05 $grid-unit-10 $grid-unit-15;
244-
text-align: left;
245-
font-weight: inherit;
246-
display: block;
247-
position: relative;
248-
height: auto;
249-
250-
&[aria-selected="true"] {
251-
color: var(--wp-admin-theme-color);
252-
253-
.components-flex-item {
254-
filter: brightness(0.95);
255-
}
256-
257-
svg {
258-
fill: var(--wp-admin-theme-color);
259-
}
260-
}
261-
262-
&::before {
263-
display: none;
264-
}
265-
}
266226
}
267227

268228
.block-editor-inserter__category-panel {

packages/components/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
- `RangeControl`: do not tooltip contents to the DOM when not shown ([#65875](https://github.com/WordPress/gutenberg/pull/65875)).
99
- `Tabs`: fix skipping indication animation glitch ([#65878](https://github.com/WordPress/gutenberg/pull/65878)).
1010

11+
### Enhancements
12+
13+
- `Tabs`: revamped vertical orientation styles ([#65387](https://github.com/WordPress/gutenberg/pull/65387)).
14+
1115
## 28.9.0 (2024-10-03)
1216

1317
### Bug Fixes

packages/components/src/tabs/styles.ts

Lines changed: 109 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,15 @@ import * as Ariakit from '@ariakit/react';
99
*/
1010
import { COLORS, CONFIG } from '../utils';
1111
import { space } from '../utils/space';
12+
import Icon from '../icon';
1213

1314
export const TabListWrapper = styled.div`
14-
position: relative;
1515
display: flex;
1616
align-items: stretch;
17-
flex-direction: row;
18-
text-align: center;
1917
overflow-x: auto;
2018
2119
&[aria-orientation='vertical'] {
2220
flex-direction: column;
23-
text-align: start;
2421
}
2522
2623
:where( [aria-orientation='horizontal'] ) {
@@ -40,11 +37,12 @@ export const TabListWrapper = styled.div`
4037
4138
@media not ( prefers-reduced-motion ) {
4239
&[data-indicator-animated]::before {
43-
transition-property: transform;
40+
transition-property: transform, border-radius, border-block;
4441
transition-duration: 0.2s;
4542
transition-timing-function: ease-out;
4643
}
4744
}
45+
position: relative;
4846
&::before {
4947
content: '';
5048
position: absolute;
@@ -59,7 +57,7 @@ export const TabListWrapper = styled.div`
5957
/* Using a large value to avoid antialiasing rounding issues
6058
when scaling in the transform, see: https://stackoverflow.com/a/52159123 */
6159
--antialiasing-factor: 100;
62-
&:not( [aria-orientation='vertical'] ) {
60+
&[aria-orientation='horizontal'] {
6361
--fade-width: 4rem;
6462
--fade-gradient-base: transparent 0%, black var( --fade-width );
6563
--fade-gradient-composed: var( --fade-gradient-base ), black 60%,
@@ -104,65 +102,87 @@ export const TabListWrapper = styled.div`
104102
${ COLORS.theme.accent };
105103
}
106104
}
107-
&[aria-orientation='vertical']::before {
108-
top: 0;
109-
left: 0;
110-
width: 100%;
111-
height: calc( var( --antialiasing-factor ) * 1px );
112-
transform: translateY( calc( var( --selected-top, 0 ) * 1px ) )
113-
scaleY(
105+
&[aria-orientation='vertical'] {
106+
&::before {
107+
/* Adjusting the border radius to match the scaling in the y axis. */
108+
border-radius: ${ CONFIG.radiusSmall } /
114109
calc(
115-
var( --selected-height, 0 ) / var( --antialiasing-factor )
116-
)
110+
${ CONFIG.radiusSmall } /
111+
(
112+
var( --selected-height, 0 ) /
113+
var( --antialiasing-factor )
114+
)
115+
);
116+
top: 0;
117+
left: 0;
118+
width: 100%;
119+
height: calc( var( --antialiasing-factor ) * 1px );
120+
transform: translateY( calc( var( --selected-top, 0 ) * 1px ) )
121+
scaleY(
122+
calc(
123+
var( --selected-height, 0 ) /
124+
var( --antialiasing-factor )
125+
)
126+
);
127+
background-color: color-mix(
128+
in srgb,
129+
${ COLORS.theme.accent },
130+
transparent 96%
117131
);
118-
background-color: ${ COLORS.theme.gray[ 100 ] };
132+
}
133+
&[data-select-on-move='true']:has(
134+
:is( :focus-visible, [data-focus-visible] )
135+
)::before {
136+
box-sizing: border-box;
137+
border: var( --wp-admin-border-width-focus ) solid
138+
${ COLORS.theme.accent };
139+
/* Adjusting the border width to match the scaling in the y axis. */
140+
border-block-width: calc(
141+
var( --wp-admin-border-width-focus, 1px ) /
142+
(
143+
var( --selected-height, 0 ) /
144+
var( --antialiasing-factor )
145+
)
146+
);
147+
}
119148
}
120149
`;
121150

122151
export const Tab = styled( Ariakit.Tab )`
123152
& {
124-
scroll-margin: 24px;
125-
flex-grow: 1;
126-
flex-shrink: 0;
127-
display: inline-flex;
128-
align-items: center;
129-
position: relative;
153+
/* Resets */
130154
border-radius: 0;
131-
height: ${ space( 12 ) };
132155
background: transparent;
133156
border: none;
134157
box-shadow: none;
158+
159+
flex: 1 0 auto;
160+
white-space: nowrap;
161+
display: flex;
162+
align-items: center;
135163
cursor: pointer;
136-
line-height: 1.2; // Some languages characters e.g. Japanese may have a native higher line-height.
137-
padding: ${ space( 4 ) };
138-
margin-left: 0;
139-
font-weight: 500;
140-
text-align: inherit;
164+
line-height: 1.2; // Characters in some languages (e.g. Japanese) may have a native higher line-height.
165+
font-weight: 400;
141166
color: ${ COLORS.theme.foreground };
142167
143168
&[aria-disabled='true'] {
144169
cursor: default;
145170
color: ${ COLORS.ui.textDisabled };
146171
}
147172
148-
&:not( [aria-disabled='true'] ):hover {
173+
&:not( [aria-disabled='true'] ):is( :hover, [data-focus-visible] ) {
149174
color: ${ COLORS.theme.accent };
150175
}
151176
152177
&:focus:not( :disabled ) {
153-
position: relative;
154178
box-shadow: none;
155179
outline: none;
156180
}
157181
158-
// Focus.
182+
// Focus indicator.
183+
position: relative;
159184
&::after {
160-
content: '';
161185
position: absolute;
162-
top: ${ space( 3 ) };
163-
right: ${ space( 3 ) };
164-
bottom: ${ space( 3 ) };
165-
left: ${ space( 3 ) };
166186
pointer-events: none;
167187
168188
// Draw the indicator.
@@ -175,23 +195,69 @@ export const Tab = styled( Ariakit.Tab )`
175195
opacity: 0;
176196
177197
@media not ( prefers-reduced-motion ) {
178-
transition: opacity 0.1s linear;
198+
transition: opacity 0.15s 0.15s linear;
179199
}
180200
}
181201
182-
&:focus-visible::after {
202+
&[data-focus-visible]::after {
183203
opacity: 1;
184204
}
185205
}
186206
207+
[aria-orientation='horizontal'] & {
208+
padding-inline: ${ space( 4 ) };
209+
height: ${ space( 12 ) };
210+
text-align: center;
211+
scroll-margin: 24px;
212+
213+
&::after {
214+
content: '';
215+
inset: ${ space( 3 ) };
216+
}
217+
}
218+
187219
[aria-orientation='vertical'] & {
188-
min-height: ${ space(
189-
10
190-
) }; // Avoid fixed height to allow for long strings that go in multiple lines.
220+
padding: ${ space( 2 ) } ${ space( 3 ) };
221+
min-height: ${ space( 10 ) };
222+
text-align: start;
223+
224+
&[aria-selected='true'] {
225+
color: ${ COLORS.theme.accent };
226+
fill: currentColor;
227+
}
228+
}
229+
[aria-orientation='vertical'][data-select-on-move='false'] &::after {
230+
content: '';
231+
inset: var( --wp-admin-border-width-focus );
191232
}
233+
`;
234+
235+
export const TabChildren = styled.span`
236+
flex-grow: 1;
237+
`;
192238

239+
export const TabChevron = styled( Icon )`
240+
flex-shrink: 0;
241+
margin-inline-end: ${ space( -1 ) };
193242
[aria-orientation='horizontal'] & {
194-
justify-content: center;
243+
display: none;
244+
}
245+
opacity: 0;
246+
[role='tab']:is( [aria-selected='true'], [data-focus-visible], :hover ) & {
247+
opacity: 1;
248+
}
249+
// The chevron is transitioned into existence when selectOnMove is enabled,
250+
// because otherwise it looks jarring, as it shows up outside of the focus
251+
// indicator that's being animated at the same time.
252+
@media not ( prefers-reduced-motion ) {
253+
[data-select-on-move='true']
254+
[role='tab']:is( [aria-selected='true'], )
255+
& {
256+
transition: opacity 0.3s ease-in;
257+
}
258+
}
259+
&:dir( rtl ) {
260+
rotate: 180deg;
195261
}
196262
`;
197263

@@ -201,7 +267,7 @@ export const TabPanel = styled( Ariakit.TabPanel )`
201267
outline: none;
202268
}
203269
204-
&:focus-visible {
270+
&[data-focus-visible] {
205271
box-shadow: 0 0 0 var( --wp-admin-border-width-focus )
206272
${ COLORS.theme.accent };
207273
// Windows high contrast mode.

packages/components/src/tabs/tab.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ import { forwardRef } from '@wordpress/element';
1010
import type { TabProps } from './types';
1111
import warning from '@wordpress/warning';
1212
import { useTabsContext } from './context';
13-
import { Tab as StyledTab } from './styles';
13+
import {
14+
Tab as StyledTab,
15+
TabChildren as StyledTabChildren,
16+
TabChevron as StyledTabChevron,
17+
} from './styles';
1418
import type { WordPressComponentProps } from '../context';
19+
import { chevronRight } from '@wordpress/icons';
1520

1621
export const Tab = forwardRef<
1722
HTMLButtonElement,
@@ -33,7 +38,8 @@ export const Tab = forwardRef<
3338
render={ render }
3439
{ ...otherProps }
3540
>
36-
{ children }
41+
<StyledTabChildren>{ children }</StyledTabChildren>
42+
<StyledTabChevron icon={ chevronRight } />
3743
</StyledTab>
3844
);
3945
} );

packages/components/src/tabs/tablist.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export const TabList = forwardRef<
115115
render={ <TabListWrapper /> }
116116
onBlur={ onBlur }
117117
tabIndex={ -1 }
118+
data-select-on-move={ selectOnMove ? 'true' : 'false' }
118119
{ ...otherProps }
119120
className={ clsx(
120121
overflow.first && 'is-overflowing-first',

test/e2e/specs/editor/various/a11y.spec.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,14 @@ test.describe( 'a11y (@firefox, @webkit)', () => {
123123
test( 'should make the modal content focusable when it is scrollable', async ( {
124124
page,
125125
pageUtils,
126+
browserName,
126127
} ) => {
128+
// eslint-disable-next-line playwright/no-skipped-test
129+
test.skip(
130+
browserName === 'webkit',
131+
'Known bug with focus order in Safari.'
132+
);
133+
127134
// Note: this test depends on a particular viewport height to determine whether or not
128135
// the modal content is scrollable. If this tests fails and needs to be debugged locally,
129136
// double-check the viewport height when running locally versus in CI. Additionally,

0 commit comments

Comments
 (0)