feat: focus outline (#37483)

* feat: focus outline

* feat: add tree focus
This commit is contained in:
MadCcc 2022-09-09 10:53:03 +08:00 committed by GitHub
parent 7e2eeb3b6e
commit 8e328d0ae2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 65 additions and 77 deletions

View File

@ -1,7 +1,7 @@
import type { CSSObject } from '@ant-design/cssinjs'; import type { CSSObject } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme'; import type { FullToken, GenerateStyle } from '../../theme';
import { genComponentStyleHook, mergeToken } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme';
import { resetComponent } from '../../style'; import { genFocusStyle, resetComponent } from '../../style';
interface BreadcrumbToken extends FullToken<'Breadcrumb'> { interface BreadcrumbToken extends FullToken<'Breadcrumb'> {
breadcrumbBaseColor: string; breadcrumbBaseColor: string;
@ -48,6 +48,8 @@ const genBreadcrumbStyle: GenerateStyle<BreadcrumbToken, CSSObject> = token => {
color: token.breadcrumbLinkColorHover, color: token.breadcrumbLinkColorHover,
backgroundColor: token.colorBgTextHover, backgroundColor: token.colorBgTextHover,
}, },
...genFocusStyle(token),
}, },
[`li:last-child > ${componentCls}-separator`]: { [`li:last-child > ${componentCls}-separator`]: {

View File

@ -2,6 +2,7 @@ import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme'; import type { FullToken, GenerateStyle } from '../../theme';
import { genComponentStyleHook, mergeToken } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme';
import genGroupStyle from './group'; import genGroupStyle from './group';
import { genFocusStyle } from '../../style';
/** Component only token. Which will handle additional calculation of alias token */ /** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {} export interface ComponentToken {}
@ -45,13 +46,17 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
[`&${componentCls}-block`]: { [`&${componentCls}-block`]: {
width: '100%', width: '100%',
}, },
'&:not(:disabled)': {
...genFocusStyle(token),
},
}, },
}; };
}; };
const genHoverActiveButtonStyle = (hoverStyle: CSSObject, activeStyle: CSSObject): CSSObject => ({ const genHoverActiveButtonStyle = (hoverStyle: CSSObject, activeStyle: CSSObject): CSSObject => ({
'&:not(:disabled)': { '&:not(:disabled)': {
'&:hover, &:focus': hoverStyle, '&:hover': hoverStyle,
'&:active': activeStyle, '&:active': activeStyle,
}, },
}); });

View File

@ -1,7 +1,7 @@
import { Keyframes } from '@ant-design/cssinjs'; import { Keyframes } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme'; import type { FullToken, GenerateStyle } from '../../theme';
import { genComponentStyleHook, mergeToken } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme';
import { resetComponent } from '../../style'; import { genFocusOutline, resetComponent } from '../../style';
export interface ComponentToken {} export interface ComponentToken {}
@ -87,6 +87,10 @@ export const genCheckboxStyle: GenerateStyle<CheckboxToken> = token => {
height: '100%', height: '100%',
cursor: 'pointer', cursor: 'pointer',
opacity: 0, opacity: 0,
[`&:focus-visible + ${checkboxCls}-inner`]: {
...genFocusOutline(token),
},
}, },
// Wrapper > Checkbox > inner // Wrapper > Checkbox > inner
@ -164,8 +168,7 @@ export const genCheckboxStyle: GenerateStyle<CheckboxToken> = token => {
${wrapperCls}:not(${wrapperCls}-disabled), ${wrapperCls}:not(${wrapperCls}-disabled),
${checkboxCls}:not(${checkboxCls}-disabled) ${checkboxCls}:not(${checkboxCls}-disabled)
`]: { `]: {
[`&:hover ${checkboxCls}-inner, [`&:hover ${checkboxCls}-inner`]: {
${checkboxCls}-input:focus + ${checkboxCls}-inner`]: {
borderColor: token.colorPrimary, borderColor: token.colorPrimary,
}, },
}, },
@ -207,8 +210,7 @@ export const genCheckboxStyle: GenerateStyle<CheckboxToken> = token => {
${wrapperCls}-checked:not(${wrapperCls}-disabled), ${wrapperCls}-checked:not(${wrapperCls}-disabled),
${checkboxCls}-checked:not(${checkboxCls}-disabled) ${checkboxCls}-checked:not(${checkboxCls}-disabled)
`]: { `]: {
[`&:hover ${checkboxCls}-inner, [`&:hover ${checkboxCls}-inner`]: {
${checkboxCls}-input:focus + ${checkboxCls}-inner`]: {
backgroundColor: token.colorPrimaryHover, backgroundColor: token.colorPrimaryHover,
borderColor: 'transparent', borderColor: 'transparent',
}, },

View File

@ -10,7 +10,7 @@ import type { FullToken, GenerateStyle } from '../../theme';
import { genComponentStyleHook, mergeToken } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme';
import genButtonStyle from './button'; import genButtonStyle from './button';
import genStatusStyle from './status'; import genStatusStyle from './status';
import { resetComponent, roundedArrow } from '../../style'; import { genFocusStyle, resetComponent, roundedArrow } from '../../style';
export interface ComponentToken { export interface ComponentToken {
zIndexPopup: number; zIndexPopup: number;
@ -267,6 +267,7 @@ const genBaseStyle: GenerateStyle<DropdownToken> = token => {
borderRadius: token.controlRadiusLG, borderRadius: token.controlRadiusLG,
outline: 'none', outline: 'none',
boxShadow: token.boxShadowSecondary, boxShadow: token.boxShadowSecondary,
...genFocusStyle(token),
[`${menuCls}-item-group-title`]: { [`${menuCls}-item-group-title`]: {
padding: `${dropdownPaddingVertical}px ${controlPaddingHorizontal}px`, padding: `${dropdownPaddingVertical}px ${controlPaddingHorizontal}px`,
@ -334,6 +335,8 @@ const genBaseStyle: GenerateStyle<DropdownToken> = token => {
backgroundColor: token.controlItemBgHover, backgroundColor: token.controlItemBgHover,
}, },
...genFocusStyle(token),
'&-selected': { '&-selected': {
color: token.colorPrimary, color: token.colorPrimary,
backgroundColor: token.controlItemBgActive, backgroundColor: token.controlItemBgActive,

View File

@ -1,13 +1,10 @@
import type { CSSInterpolation } from '@ant-design/cssinjs'; import type { CSSInterpolation } from '@ant-design/cssinjs';
import { genFocusOutline } from '../../style';
import type { MenuToken } from '.'; import type { MenuToken } from '.';
const accessibilityFocus = (token: MenuToken) => { const accessibilityFocus = (token: MenuToken) => ({
const { controlOutlineWidth, colorPrimaryHover } = token; ...genFocusOutline(token),
});
return {
boxShadow: `0 0 0 ${controlOutlineWidth}px ${colorPrimaryHover}`,
};
};
const getThemeStyle = (token: MenuToken): CSSInterpolation => { const getThemeStyle = (token: MenuToken): CSSInterpolation => {
const { const {

View File

@ -7,7 +7,7 @@ import {
} from '../../input/style'; } from '../../input/style';
import type { FullToken, GenerateStyle } from '../../theme'; import type { FullToken, GenerateStyle } from '../../theme';
import { genComponentStyleHook, mergeToken } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme';
import { resetComponent } from '../../style'; import { genFocusOutline, genFocusStyle, resetComponent } from '../../style';
interface PaginationToken extends InputToken<FullToken<'Pagination'>> { interface PaginationToken extends InputToken<FullToken<'Pagination'>> {
paginationItemSize: number; paginationItemSize: number;
@ -209,7 +209,7 @@ const genPaginationSimpleStyle: GenerateStyle<PaginationToken, CSSObject> = toke
border: `${token.controlLineWidth}px ${token.controlLineType} ${token.colorBorder}`, border: `${token.controlLineWidth}px ${token.controlLineType} ${token.colorBorder}`,
borderRadius: token.radiusBase, borderRadius: token.radiusBase,
outline: 'none', outline: 'none',
transition: `border-color ${token.motionDurationSlow}`, transition: `border-color ${token.motionDurationFast}`,
'&:hover': { '&:hover': {
borderColor: token.colorPrimary, borderColor: token.colorPrimary,
@ -217,7 +217,7 @@ const genPaginationSimpleStyle: GenerateStyle<PaginationToken, CSSObject> = toke
'&:focus': { '&:focus': {
borderColor: token.colorPrimaryHover, borderColor: token.colorPrimaryHover,
boxShadow: `${token.inputOutlineOffset} 0 ${token.controlOutlineWidth} ${token.controlOutline}`, boxShadow: `${token.inputOutlineOffset}px 0 ${token.controlOutlineWidth}px ${token.controlOutline}`,
}, },
'&[disabled]': { '&[disabled]': {
@ -290,6 +290,7 @@ const genPaginationJumpStyle: GenerateStyle<PaginationToken, CSSObject> = token
[`${componentCls}-item-ellipsis`]: { [`${componentCls}-item-ellipsis`]: {
opacity: 0, opacity: 0,
}, },
...genFocusOutline(token),
}, },
}, },
@ -318,7 +319,7 @@ const genPaginationJumpStyle: GenerateStyle<PaginationToken, CSSObject> = token
listStyle: 'none', listStyle: 'none',
borderRadius: token.radiusBase, borderRadius: token.radiusBase,
cursor: 'pointer', cursor: 'pointer',
transition: `all ${token.motionDurationSlow}`, transition: `all ${token.motionDurationFast}`,
}, },
[`${componentCls}-prev, ${componentCls}-next`]: { [`${componentCls}-prev, ${componentCls}-next`]: {
@ -342,12 +343,11 @@ const genPaginationJumpStyle: GenerateStyle<PaginationToken, CSSObject> = token
border: `${token.controlLineWidth}px ${token.controlLineType} transparent`, border: `${token.controlLineWidth}px ${token.controlLineType} transparent`,
borderRadius: token.radiusBase, borderRadius: token.radiusBase,
outline: 'none', outline: 'none',
transition: `all ${token.motionDurationSlow}`, transition: `border ${token.motionDurationFast}`,
}, },
[`&:focus-visible ${componentCls}-item-link`]: { [`&:focus-visible ${componentCls}-item-link`]: {
color: token.colorPrimary, ...genFocusOutline(token),
borderColor: token.colorPrimary,
}, },
[`&:hover ${componentCls}-item-link`]: { [`&:hover ${componentCls}-item-link`]: {
@ -424,7 +424,7 @@ const genPaginationItemStyle: GenerateStyle<PaginationToken, CSSObject> = token
}, },
'&:hover': { '&:hover': {
transition: `all ${token.motionDurationSlow}`, transition: `all ${token.motionDurationFast}`,
a: { a: {
color: token.colorPrimary, color: token.colorPrimary,
@ -433,14 +433,7 @@ const genPaginationItemStyle: GenerateStyle<PaginationToken, CSSObject> = token
// cannot merge with `&:hover` // cannot merge with `&:hover`
// see https://github.com/ant-design/ant-design/pull/34002 // see https://github.com/ant-design/ant-design/pull/34002
'&:focus-visible': { ...genFocusStyle(token),
borderColor: token.colorPrimary,
transition: `all ${token.motionDurationSlow}`,
a: {
color: token.colorPrimary,
},
},
'&-active': { '&-active': {
fontWeight: token.paginationFontWeightActive, fontWeight: token.paginationFontWeightActive,
@ -455,17 +448,9 @@ const genPaginationItemStyle: GenerateStyle<PaginationToken, CSSObject> = token
borderColor: token.colorPrimaryHover, borderColor: token.colorPrimaryHover,
}, },
'&:focus-visible': {
borderColor: token.colorPrimaryHover,
},
'&:hover a': { '&:hover a': {
color: token.colorPrimaryHover, color: token.colorPrimaryHover,
}, },
'&:focus-visible a': {
color: token.colorPrimaryHover,
},
}, },
}, },
}; };

View File

@ -1,7 +1,7 @@
import { Keyframes } from '@ant-design/cssinjs'; import { Keyframes } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme'; import type { FullToken, GenerateStyle } from '../../theme';
import { genComponentStyleHook, mergeToken } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme';
import { resetComponent } from '../../style'; import { genFocusOutline, resetComponent } from '../../style';
// ============================== Tokens ============================== // ============================== Tokens ==============================
export interface ComponentToken {} export interface ComponentToken {}
@ -69,7 +69,6 @@ const getRadioBasicStyle: GenerateStyle<RadioToken> = token => {
radioWrapperMarginRight, radioWrapperMarginRight,
radioDotColor, radioDotColor,
radioTop, radioTop,
radioFocusShadow,
radioSize, radioSize,
motionDurationSlow, motionDurationSlow,
motionDurationFast, motionDurationFast,
@ -141,13 +140,12 @@ const getRadioBasicStyle: GenerateStyle<RadioToken> = token => {
}, },
[`${componentCls}-wrapper:hover &, [`${componentCls}-wrapper:hover &,
&:hover ${radioInnerPrefixCls}, &:hover ${radioInnerPrefixCls}`]: {
&-input:focus + ${radioInnerPrefixCls}`]: {
borderColor: radioDotColor, borderColor: radioDotColor,
}, },
[`${componentCls}-input:focus + ${radioInnerPrefixCls}`]: { [`${componentCls}-input:focus-visible + ${radioInnerPrefixCls}`]: {
boxShadow: radioFocusShadow, ...genFocusOutline(token),
}, },
[`${componentCls}:hover::after, ${componentCls}-wrapper:hover &::after`]: { [`${componentCls}:hover::after, ${componentCls}-wrapper:hover &::after`]: {
@ -274,7 +272,6 @@ const getRadioButtonStyle: GenerateStyle<RadioToken> = token => {
controlRadiusSM, controlRadiusSM,
controlRadiusLG, controlRadiusLG,
radioDotColor, radioDotColor,
radioButtonFocusShadow,
radioButtonCheckedBg, radioButtonCheckedBg,
radioButtonHoverColor, radioButtonHoverColor,
radioButtonActiveColor, radioButtonActiveColor,
@ -393,8 +390,8 @@ const getRadioButtonStyle: GenerateStyle<RadioToken> = token => {
color: radioDotColor, color: radioDotColor,
}, },
'&:focus-within': { '&:has(:focus-visible)': {
boxShadow: radioButtonFocusShadow, ...genFocusOutline(token),
}, },
[`${componentCls}-inner, input[type='checkbox'], input[type='radio']`]: { [`${componentCls}-inner, input[type='checkbox'], input[type='radio']`]: {
@ -435,10 +432,6 @@ const getRadioButtonStyle: GenerateStyle<RadioToken> = token => {
backgroundColor: radioButtonActiveColor, backgroundColor: radioButtonActiveColor,
}, },
}, },
'&:focus-within': {
boxShadow: radioButtonFocusShadow,
},
}, },
[`${componentCls}-group-solid &-checked:not(&-disabled)`]: { [`${componentCls}-group-solid &-checked:not(&-disabled)`]: {
@ -457,10 +450,6 @@ const getRadioButtonStyle: GenerateStyle<RadioToken> = token => {
background: radioButtonActiveColor, background: radioButtonActiveColor,
borderColor: radioButtonActiveColor, borderColor: radioButtonActiveColor,
}, },
'&:focus-within': {
boxShadow: radioButtonFocusShadow,
},
}, },
'&-disabled': { '&-disabled': {

View File

@ -86,7 +86,7 @@ const genBaseStyle: GenerateStyle<SliderToken> = token => {
zIndex: 1, zIndex: 1,
}, },
'&:hover, &:active, &:focus': { '&:hover, &:active, &:focus-visible': {
boxShadow: `none`, boxShadow: `none`,
outlineWidth: token.handleLineWidthHover, outlineWidth: token.handleLineWidthHover,
outlineColor: token.colorPrimary, outlineColor: token.colorPrimary,

View File

@ -95,3 +95,15 @@ export const genLinkStyle = (token: DerivativeToken): CSSObject => ({
}, },
}, },
}); });
export const genFocusOutline = (token: DerivativeToken): CSSObject => ({
outline: `${token.lineWidth * 4}px solid ${token.colorPrimaryBorder}`,
outlineOffset: 1,
transition: 'outline-offset 0s, outline 0s',
});
export const genFocusStyle = (token: DerivativeToken): CSSObject => ({
'&:focus-visible': {
...genFocusOutline(token),
},
});

View File

@ -28,7 +28,7 @@ body {
margin: 0; margin: 0;
} }
[tabindex='-1']:focus { [tabindex='-1']:focus {
outline: none !important; outline: none;
} }
hr { hr {
box-sizing: content-box; box-sizing: content-box;

View File

@ -2,7 +2,7 @@ import type { CSSObject } from '@ant-design/cssinjs';
import { TinyColor } from '@ctrl/tinycolor'; import { TinyColor } from '@ctrl/tinycolor';
import type { FullToken, GenerateStyle } from '../../theme'; import type { FullToken, GenerateStyle } from '../../theme';
import { genComponentStyleHook, mergeToken } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme';
import { resetComponent } from '../../style'; import { genFocusStyle, resetComponent } from '../../style';
interface SwitchToken extends FullToken<'Switch'> { interface SwitchToken extends FullToken<'Switch'> {
switchMinWidth: number; switchMinWidth: number;
@ -163,18 +163,7 @@ const genSwitchStyle = (token: SwitchToken): CSSObject => {
background: token.colorTextTertiary, background: token.colorTextTertiary,
}, },
'&:focus-visible': { ...genFocusStyle(token),
outline: 0,
boxShadow: `0 0 0 ${token.controlOutlineWidth}px ${token.controlTmpOutline}`,
},
[`&${token.componentCls}-checked:focus-visible`]: {
boxShadow: `0 0 0 ${token.controlOutlineWidth}px ${token.controlOutline}`,
},
'&:focus:hover': {
boxShadow: 'none',
},
[`&${token.componentCls}-checked`]: { [`&${token.componentCls}-checked`]: {
background: token.switchColor, background: token.switchColor,

View File

@ -1,7 +1,7 @@
import type { CSSObject } from '@ant-design/cssinjs'; import type { CSSObject } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme'; import type { FullToken, GenerateStyle } from '../../theme';
import { genComponentStyleHook, mergeToken } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme';
import { resetComponent } from '../../style'; import { genFocusStyle, resetComponent } from '../../style';
import genMotionStyle from './motion'; import genMotionStyle from './motion';
export interface ComponentToken { export interface ComponentToken {
@ -542,9 +542,10 @@ const genTabStyle: GenerateStyle<TabsToken, CSSObject> = (token: TabsToken) => {
outline: 'none', outline: 'none',
cursor: 'pointer', cursor: 'pointer',
'&-btn, &-remove': { '&-btn, &-remove': {
'&:focus, &:active': { '&:focus:not(:focus-visible), &:active': {
color: tabsActiveColor, color: tabsActiveColor,
}, },
...genFocusStyle(token),
}, },
'&-btn': { '&-btn': {
outline: 'none', outline: 'none',
@ -809,9 +810,11 @@ const genTabsStyle: GenerateStyle<TabsToken> = (token: TabsToken): CSSObject =>
color: tabsHoverColor, color: tabsHoverColor,
}, },
'&:active, &:focus': { '&:active, &:focus:not(:focus-visible)': {
color: tabsActiveColor, color: tabsActiveColor,
}, },
...genFocusStyle(token),
}, },
}, },

View File

@ -4,7 +4,7 @@ import { genCollapseMotion } from '../../style/motion';
import { getStyle as getCheckboxStyle } from '../../checkbox/style'; import { getStyle as getCheckboxStyle } from '../../checkbox/style';
import type { DerivativeToken } from '../../theme'; import type { DerivativeToken } from '../../theme';
import { genComponentStyleHook, mergeToken } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme';
import { resetComponent } from '../../style'; import { genFocusOutline, resetComponent } from '../../style';
// ============================ Keyframes ============================= // ============================ Keyframes =============================
const treeNodeFX = new Keyframes('ant-tree-node-fx-do-not-use', { const treeNodeFX = new Keyframes('ant-tree-node-fx-do-not-use', {
@ -89,7 +89,7 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
}, },
'&-focused:not(:hover):not(&-active-focused)': { '&-focused:not(:hover):not(&-active-focused)': {
background: token.controlOutline, ...genFocusOutline(token),
}, },
// =================== Virtual List =================== // =================== Virtual List ===================
@ -153,7 +153,7 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
}, },
[`&-active ${treeCls}-node-content-wrapper`]: { [`&-active ${treeCls}-node-content-wrapper`]: {
background: token.controlItemBgHover, ...genFocusOutline(token),
}, },
[`&:not(&-disabled).filter-node ${treeCls}-title`]: { [`&:not(&-disabled).filter-node ${treeCls}-title`]: {

View File

@ -25,6 +25,7 @@ const genTypographyStyle: GenerateStyle<TypographyToken> = token => {
[componentCls]: { [componentCls]: {
color: token.colorText, color: token.colorText,
overflowWrap: 'break-word', overflowWrap: 'break-word',
lineHeight: token.lineHeight,
'&&-secondary': { '&&-secondary': {
color: token.colorTextDescription, color: token.colorTextDescription,
}, },