From 8e328d0ae24b512bc240fc1883f2ef342eb3d86b Mon Sep 17 00:00:00 2001 From: MadCcc <1075746765@qq.com> Date: Fri, 9 Sep 2022 10:53:03 +0800 Subject: [PATCH] feat: focus outline (#37483) * feat: focus outline * feat: add tree focus --- components/breadcrumb/style/index.tsx | 4 +++- components/button/style/index.tsx | 7 +++++- components/checkbox/style/index.tsx | 12 ++++++---- components/dropdown/style/index.tsx | 5 +++- components/menu/style/theme.tsx | 11 ++++----- components/pagination/style/index.tsx | 33 ++++++++------------------- components/radio/style/index.tsx | 23 +++++-------------- components/slider/style/index.tsx | 2 +- components/style/index.tsx | 12 ++++++++++ components/style/reset.css | 2 +- components/switch/style/index.tsx | 15 ++---------- components/tabs/style/index.tsx | 9 +++++--- components/tree/style/index.tsx | 6 ++--- components/typography/style/index.tsx | 1 + 14 files changed, 65 insertions(+), 77 deletions(-) diff --git a/components/breadcrumb/style/index.tsx b/components/breadcrumb/style/index.tsx index 3e20a2b3dc..2aa93bc1fb 100644 --- a/components/breadcrumb/style/index.tsx +++ b/components/breadcrumb/style/index.tsx @@ -1,7 +1,7 @@ import type { CSSObject } from '@ant-design/cssinjs'; import type { FullToken, GenerateStyle } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme'; -import { resetComponent } from '../../style'; +import { genFocusStyle, resetComponent } from '../../style'; interface BreadcrumbToken extends FullToken<'Breadcrumb'> { breadcrumbBaseColor: string; @@ -48,6 +48,8 @@ const genBreadcrumbStyle: GenerateStyle = token => { color: token.breadcrumbLinkColorHover, backgroundColor: token.colorBgTextHover, }, + + ...genFocusStyle(token), }, [`li:last-child > ${componentCls}-separator`]: { diff --git a/components/button/style/index.tsx b/components/button/style/index.tsx index c49d1752a9..a7e065de4d 100644 --- a/components/button/style/index.tsx +++ b/components/button/style/index.tsx @@ -2,6 +2,7 @@ import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs'; import type { FullToken, GenerateStyle } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme'; import genGroupStyle from './group'; +import { genFocusStyle } from '../../style'; /** Component only token. Which will handle additional calculation of alias token */ export interface ComponentToken {} @@ -45,13 +46,17 @@ const genSharedButtonStyle: GenerateStyle = (token): CSS [`&${componentCls}-block`]: { width: '100%', }, + + '&:not(:disabled)': { + ...genFocusStyle(token), + }, }, }; }; const genHoverActiveButtonStyle = (hoverStyle: CSSObject, activeStyle: CSSObject): CSSObject => ({ '&:not(:disabled)': { - '&:hover, &:focus': hoverStyle, + '&:hover': hoverStyle, '&:active': activeStyle, }, }); diff --git a/components/checkbox/style/index.tsx b/components/checkbox/style/index.tsx index 848d249c90..d2fc0daaa0 100644 --- a/components/checkbox/style/index.tsx +++ b/components/checkbox/style/index.tsx @@ -1,7 +1,7 @@ import { Keyframes } from '@ant-design/cssinjs'; import type { FullToken, GenerateStyle } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme'; -import { resetComponent } from '../../style'; +import { genFocusOutline, resetComponent } from '../../style'; export interface ComponentToken {} @@ -87,6 +87,10 @@ export const genCheckboxStyle: GenerateStyle = token => { height: '100%', cursor: 'pointer', opacity: 0, + + [`&:focus-visible + ${checkboxCls}-inner`]: { + ...genFocusOutline(token), + }, }, // Wrapper > Checkbox > inner @@ -164,8 +168,7 @@ export const genCheckboxStyle: GenerateStyle = token => { ${wrapperCls}:not(${wrapperCls}-disabled), ${checkboxCls}:not(${checkboxCls}-disabled) `]: { - [`&:hover ${checkboxCls}-inner, - ${checkboxCls}-input:focus + ${checkboxCls}-inner`]: { + [`&:hover ${checkboxCls}-inner`]: { borderColor: token.colorPrimary, }, }, @@ -207,8 +210,7 @@ export const genCheckboxStyle: GenerateStyle = token => { ${wrapperCls}-checked:not(${wrapperCls}-disabled), ${checkboxCls}-checked:not(${checkboxCls}-disabled) `]: { - [`&:hover ${checkboxCls}-inner, - ${checkboxCls}-input:focus + ${checkboxCls}-inner`]: { + [`&:hover ${checkboxCls}-inner`]: { backgroundColor: token.colorPrimaryHover, borderColor: 'transparent', }, diff --git a/components/dropdown/style/index.tsx b/components/dropdown/style/index.tsx index 90e06adf93..6a5a9251b3 100644 --- a/components/dropdown/style/index.tsx +++ b/components/dropdown/style/index.tsx @@ -10,7 +10,7 @@ import type { FullToken, GenerateStyle } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme'; import genButtonStyle from './button'; import genStatusStyle from './status'; -import { resetComponent, roundedArrow } from '../../style'; +import { genFocusStyle, resetComponent, roundedArrow } from '../../style'; export interface ComponentToken { zIndexPopup: number; @@ -267,6 +267,7 @@ const genBaseStyle: GenerateStyle = token => { borderRadius: token.controlRadiusLG, outline: 'none', boxShadow: token.boxShadowSecondary, + ...genFocusStyle(token), [`${menuCls}-item-group-title`]: { padding: `${dropdownPaddingVertical}px ${controlPaddingHorizontal}px`, @@ -334,6 +335,8 @@ const genBaseStyle: GenerateStyle = token => { backgroundColor: token.controlItemBgHover, }, + ...genFocusStyle(token), + '&-selected': { color: token.colorPrimary, backgroundColor: token.controlItemBgActive, diff --git a/components/menu/style/theme.tsx b/components/menu/style/theme.tsx index d41f2250d2..9253981807 100644 --- a/components/menu/style/theme.tsx +++ b/components/menu/style/theme.tsx @@ -1,13 +1,10 @@ import type { CSSInterpolation } from '@ant-design/cssinjs'; +import { genFocusOutline } from '../../style'; import type { MenuToken } from '.'; -const accessibilityFocus = (token: MenuToken) => { - const { controlOutlineWidth, colorPrimaryHover } = token; - - return { - boxShadow: `0 0 0 ${controlOutlineWidth}px ${colorPrimaryHover}`, - }; -}; +const accessibilityFocus = (token: MenuToken) => ({ + ...genFocusOutline(token), +}); const getThemeStyle = (token: MenuToken): CSSInterpolation => { const { diff --git a/components/pagination/style/index.tsx b/components/pagination/style/index.tsx index 6263f705d2..4da0898b1d 100644 --- a/components/pagination/style/index.tsx +++ b/components/pagination/style/index.tsx @@ -7,7 +7,7 @@ import { } from '../../input/style'; import type { FullToken, GenerateStyle } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme'; -import { resetComponent } from '../../style'; +import { genFocusOutline, genFocusStyle, resetComponent } from '../../style'; interface PaginationToken extends InputToken> { paginationItemSize: number; @@ -209,7 +209,7 @@ const genPaginationSimpleStyle: GenerateStyle = toke border: `${token.controlLineWidth}px ${token.controlLineType} ${token.colorBorder}`, borderRadius: token.radiusBase, outline: 'none', - transition: `border-color ${token.motionDurationSlow}`, + transition: `border-color ${token.motionDurationFast}`, '&:hover': { borderColor: token.colorPrimary, @@ -217,7 +217,7 @@ const genPaginationSimpleStyle: GenerateStyle = toke '&:focus': { borderColor: token.colorPrimaryHover, - boxShadow: `${token.inputOutlineOffset} 0 ${token.controlOutlineWidth} ${token.controlOutline}`, + boxShadow: `${token.inputOutlineOffset}px 0 ${token.controlOutlineWidth}px ${token.controlOutline}`, }, '&[disabled]': { @@ -290,6 +290,7 @@ const genPaginationJumpStyle: GenerateStyle = token [`${componentCls}-item-ellipsis`]: { opacity: 0, }, + ...genFocusOutline(token), }, }, @@ -318,7 +319,7 @@ const genPaginationJumpStyle: GenerateStyle = token listStyle: 'none', borderRadius: token.radiusBase, cursor: 'pointer', - transition: `all ${token.motionDurationSlow}`, + transition: `all ${token.motionDurationFast}`, }, [`${componentCls}-prev, ${componentCls}-next`]: { @@ -342,12 +343,11 @@ const genPaginationJumpStyle: GenerateStyle = token border: `${token.controlLineWidth}px ${token.controlLineType} transparent`, borderRadius: token.radiusBase, outline: 'none', - transition: `all ${token.motionDurationSlow}`, + transition: `border ${token.motionDurationFast}`, }, [`&:focus-visible ${componentCls}-item-link`]: { - color: token.colorPrimary, - borderColor: token.colorPrimary, + ...genFocusOutline(token), }, [`&:hover ${componentCls}-item-link`]: { @@ -424,7 +424,7 @@ const genPaginationItemStyle: GenerateStyle = token }, '&:hover': { - transition: `all ${token.motionDurationSlow}`, + transition: `all ${token.motionDurationFast}`, a: { color: token.colorPrimary, @@ -433,14 +433,7 @@ const genPaginationItemStyle: GenerateStyle = token // cannot merge with `&:hover` // see https://github.com/ant-design/ant-design/pull/34002 - '&:focus-visible': { - borderColor: token.colorPrimary, - transition: `all ${token.motionDurationSlow}`, - - a: { - color: token.colorPrimary, - }, - }, + ...genFocusStyle(token), '&-active': { fontWeight: token.paginationFontWeightActive, @@ -455,17 +448,9 @@ const genPaginationItemStyle: GenerateStyle = token borderColor: token.colorPrimaryHover, }, - '&:focus-visible': { - borderColor: token.colorPrimaryHover, - }, - '&:hover a': { color: token.colorPrimaryHover, }, - - '&:focus-visible a': { - color: token.colorPrimaryHover, - }, }, }, }; diff --git a/components/radio/style/index.tsx b/components/radio/style/index.tsx index 14017c1b86..12438186c1 100644 --- a/components/radio/style/index.tsx +++ b/components/radio/style/index.tsx @@ -1,7 +1,7 @@ import { Keyframes } from '@ant-design/cssinjs'; import type { FullToken, GenerateStyle } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme'; -import { resetComponent } from '../../style'; +import { genFocusOutline, resetComponent } from '../../style'; // ============================== Tokens ============================== export interface ComponentToken {} @@ -69,7 +69,6 @@ const getRadioBasicStyle: GenerateStyle = token => { radioWrapperMarginRight, radioDotColor, radioTop, - radioFocusShadow, radioSize, motionDurationSlow, motionDurationFast, @@ -141,13 +140,12 @@ const getRadioBasicStyle: GenerateStyle = token => { }, [`${componentCls}-wrapper:hover &, - &:hover ${radioInnerPrefixCls}, - &-input:focus + ${radioInnerPrefixCls}`]: { + &:hover ${radioInnerPrefixCls}`]: { borderColor: radioDotColor, }, - [`${componentCls}-input:focus + ${radioInnerPrefixCls}`]: { - boxShadow: radioFocusShadow, + [`${componentCls}-input:focus-visible + ${radioInnerPrefixCls}`]: { + ...genFocusOutline(token), }, [`${componentCls}:hover::after, ${componentCls}-wrapper:hover &::after`]: { @@ -274,7 +272,6 @@ const getRadioButtonStyle: GenerateStyle = token => { controlRadiusSM, controlRadiusLG, radioDotColor, - radioButtonFocusShadow, radioButtonCheckedBg, radioButtonHoverColor, radioButtonActiveColor, @@ -393,8 +390,8 @@ const getRadioButtonStyle: GenerateStyle = token => { color: radioDotColor, }, - '&:focus-within': { - boxShadow: radioButtonFocusShadow, + '&:has(:focus-visible)': { + ...genFocusOutline(token), }, [`${componentCls}-inner, input[type='checkbox'], input[type='radio']`]: { @@ -435,10 +432,6 @@ const getRadioButtonStyle: GenerateStyle = token => { backgroundColor: radioButtonActiveColor, }, }, - - '&:focus-within': { - boxShadow: radioButtonFocusShadow, - }, }, [`${componentCls}-group-solid &-checked:not(&-disabled)`]: { @@ -457,10 +450,6 @@ const getRadioButtonStyle: GenerateStyle = token => { background: radioButtonActiveColor, borderColor: radioButtonActiveColor, }, - - '&:focus-within': { - boxShadow: radioButtonFocusShadow, - }, }, '&-disabled': { diff --git a/components/slider/style/index.tsx b/components/slider/style/index.tsx index 84559fa96b..5ed0b421b4 100644 --- a/components/slider/style/index.tsx +++ b/components/slider/style/index.tsx @@ -86,7 +86,7 @@ const genBaseStyle: GenerateStyle = token => { zIndex: 1, }, - '&:hover, &:active, &:focus': { + '&:hover, &:active, &:focus-visible': { boxShadow: `none`, outlineWidth: token.handleLineWidthHover, outlineColor: token.colorPrimary, diff --git a/components/style/index.tsx b/components/style/index.tsx index dca5511022..0926e83660 100644 --- a/components/style/index.tsx +++ b/components/style/index.tsx @@ -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), + }, +}); diff --git a/components/style/reset.css b/components/style/reset.css index a568915c1d..670ad3d1a5 100644 --- a/components/style/reset.css +++ b/components/style/reset.css @@ -28,7 +28,7 @@ body { margin: 0; } [tabindex='-1']:focus { - outline: none !important; + outline: none; } hr { box-sizing: content-box; diff --git a/components/switch/style/index.tsx b/components/switch/style/index.tsx index a9b3ede6db..03372ccc3e 100644 --- a/components/switch/style/index.tsx +++ b/components/switch/style/index.tsx @@ -2,7 +2,7 @@ import type { CSSObject } from '@ant-design/cssinjs'; import { TinyColor } from '@ctrl/tinycolor'; import type { FullToken, GenerateStyle } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme'; -import { resetComponent } from '../../style'; +import { genFocusStyle, resetComponent } from '../../style'; interface SwitchToken extends FullToken<'Switch'> { switchMinWidth: number; @@ -163,18 +163,7 @@ const genSwitchStyle = (token: SwitchToken): CSSObject => { background: token.colorTextTertiary, }, - '&:focus-visible': { - 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', - }, + ...genFocusStyle(token), [`&${token.componentCls}-checked`]: { background: token.switchColor, diff --git a/components/tabs/style/index.tsx b/components/tabs/style/index.tsx index 238528a617..bf4d7c4b0c 100644 --- a/components/tabs/style/index.tsx +++ b/components/tabs/style/index.tsx @@ -1,7 +1,7 @@ import type { CSSObject } from '@ant-design/cssinjs'; import type { FullToken, GenerateStyle } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme'; -import { resetComponent } from '../../style'; +import { genFocusStyle, resetComponent } from '../../style'; import genMotionStyle from './motion'; export interface ComponentToken { @@ -542,9 +542,10 @@ const genTabStyle: GenerateStyle = (token: TabsToken) => { outline: 'none', cursor: 'pointer', '&-btn, &-remove': { - '&:focus, &:active': { + '&:focus:not(:focus-visible), &:active': { color: tabsActiveColor, }, + ...genFocusStyle(token), }, '&-btn': { outline: 'none', @@ -809,9 +810,11 @@ const genTabsStyle: GenerateStyle = (token: TabsToken): CSSObject => color: tabsHoverColor, }, - '&:active, &:focus': { + '&:active, &:focus:not(:focus-visible)': { color: tabsActiveColor, }, + + ...genFocusStyle(token), }, }, diff --git a/components/tree/style/index.tsx b/components/tree/style/index.tsx index 576840ae02..3d6781e6fc 100644 --- a/components/tree/style/index.tsx +++ b/components/tree/style/index.tsx @@ -4,7 +4,7 @@ import { genCollapseMotion } from '../../style/motion'; import { getStyle as getCheckboxStyle } from '../../checkbox/style'; import type { DerivativeToken } from '../../theme'; import { genComponentStyleHook, mergeToken } from '../../theme'; -import { resetComponent } from '../../style'; +import { genFocusOutline, resetComponent } from '../../style'; // ============================ Keyframes ============================= 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)': { - background: token.controlOutline, + ...genFocusOutline(token), }, // =================== Virtual List =================== @@ -153,7 +153,7 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject => }, [`&-active ${treeCls}-node-content-wrapper`]: { - background: token.controlItemBgHover, + ...genFocusOutline(token), }, [`&:not(&-disabled).filter-node ${treeCls}-title`]: { diff --git a/components/typography/style/index.tsx b/components/typography/style/index.tsx index b380192b84..48c3202aef 100644 --- a/components/typography/style/index.tsx +++ b/components/typography/style/index.tsx @@ -25,6 +25,7 @@ const genTypographyStyle: GenerateStyle = token => { [componentCls]: { color: token.colorText, overflowWrap: 'break-word', + lineHeight: token.lineHeight, '&&-secondary': { color: token.colorTextDescription, },