From 27e18a7f92480c389ac6e8b2d8378c2d8af72e21 Mon Sep 17 00:00:00 2001 From: cheton Date: Fri, 29 Sep 2023 12:29:57 +0800 Subject: [PATCH] feat: implement `:focus-visible` for the Tabs component with targeted focus style for non-pointer devices --- .../__tests__/__snapshots__/Tabs.test.js.snap | 102 ++++++------- packages/react/src/tabs/styles.js | 135 +++++++++++------- 2 files changed, 129 insertions(+), 108 deletions(-) diff --git a/packages/react/src/tabs/__tests__/__snapshots__/Tabs.test.js.snap b/packages/react/src/tabs/__tests__/__snapshots__/Tabs.test.js.snap index e47300e178..18d48e8c73 100644 --- a/packages/react/src/tabs/__tests__/__snapshots__/Tabs.test.js.snap +++ b/packages/react/src/tabs/__tests__/__snapshots__/Tabs.test.js.snap @@ -34,9 +34,6 @@ exports[`Tabs should render correctly 1`] = ` padding: 0; font-size: var(--tonic-fontSizes-sm); line-height: var(--tonic-lineHeights-sm); - border-color: var(--tonic-colors-transparent); - border-style: solid; - border-width: var(--tonic-sizes-1h); display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -45,40 +42,37 @@ exports[`Tabs should render correctly 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - padding-left: var(--tonic-space-3x); - padding-right: var(--tonic-space-3x); - padding-top: var(--tonic-space-2x); + padding-left: var(--tonic-space-4x); + padding-right: var(--tonic-space-4x); + padding-top: calc(.5rem + .125rem); padding-bottom: var(--tonic-space-2x); + border-bottom-color: var(--tonic-colors-transparent); + border-bottom-style: solid; + border-bottom-width: var(--tonic-sizes-1h); +} + +.emotion-4:focus-visible { + outline-color: var(--tonic-colors-blue-60); + outline-width: var(--tonic-sizes-1h); + outline-style: solid; + outline-offset: calc(var(--tonic-space-1h) * -1); + color: var(--tonic-colors-white-primary); } .emotion-4[aria-selected=true], .emotion-4[data-selected] { - border: none; border-bottom-color: var(--tonic-colors-red-60); border-bottom-style: solid; border-bottom-width: var(--tonic-sizes-1h); color: var(--tonic-colors-white-primary); - padding-left: calc(.75rem + .125rem); - padding-right: calc(.75rem + .125rem); - padding-top: calc(.5rem + .125rem); -} - -.emotion-4:focus, -.emotion-4[data-focus] { - border-color: var(--tonic-colors-red-60); - color: var(--tonic-colors-white-primary); } .emotion-4:hover, .emotion-4[data-hover] { - border: none; border-bottom-color: var(--tonic-colors-red-60); border-bottom-style: solid; border-bottom-width: var(--tonic-sizes-1h); color: var(--tonic-colors-white-primary); - padding-left: calc(.75rem + .125rem); - padding-right: calc(.75rem + .125rem); - padding-top: calc(.5rem + .125rem); } .emotion-6 { @@ -93,9 +87,6 @@ exports[`Tabs should render correctly 1`] = ` padding: 0; font-size: var(--tonic-fontSizes-sm); line-height: var(--tonic-lineHeights-sm); - border-color: var(--tonic-colors-transparent); - border-style: solid; - border-width: var(--tonic-sizes-1h); display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -104,40 +95,37 @@ exports[`Tabs should render correctly 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - padding-left: var(--tonic-space-3x); - padding-right: var(--tonic-space-3x); - padding-top: var(--tonic-space-2x); + padding-left: var(--tonic-space-4x); + padding-right: var(--tonic-space-4x); + padding-top: calc(.5rem + .125rem); padding-bottom: var(--tonic-space-2x); + border-bottom-color: var(--tonic-colors-transparent); + border-bottom-style: solid; + border-bottom-width: var(--tonic-sizes-1h); +} + +.emotion-6:focus-visible { + outline-color: var(--tonic-colors-blue-60); + outline-width: var(--tonic-sizes-1h); + outline-style: solid; + outline-offset: calc(var(--tonic-space-1h) * -1); + color: var(--tonic-colors-white-primary); } .emotion-6[aria-selected=true], .emotion-6[data-selected] { - border: none; border-bottom-color: var(--tonic-colors-red-60); border-bottom-style: solid; border-bottom-width: var(--tonic-sizes-1h); color: var(--tonic-colors-white-primary); - padding-left: calc(.75rem + .125rem); - padding-right: calc(.75rem + .125rem); - padding-top: calc(.5rem + .125rem); -} - -.emotion-6:focus, -.emotion-6[data-focus] { - border-color: var(--tonic-colors-blue-60); - color: var(--tonic-colors-white-primary); } .emotion-6:hover, .emotion-6[data-hover] { - border: none; border-bottom-color: var(--tonic-colors-gray-60); border-bottom-style: solid; border-bottom-width: var(--tonic-sizes-1h); color: var(--tonic-colors-white-primary); - padding-left: calc(.75rem + .125rem); - padding-right: calc(.75rem + .125rem); - padding-top: calc(.5rem + .125rem); } .emotion-8 { @@ -153,9 +141,6 @@ exports[`Tabs should render correctly 1`] = ` padding: 0; font-size: var(--tonic-fontSizes-sm); line-height: var(--tonic-lineHeights-sm); - border-color: var(--tonic-colors-transparent); - border-style: solid; - border-width: var(--tonic-sizes-1h); display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -164,40 +149,37 @@ exports[`Tabs should render correctly 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - padding-left: var(--tonic-space-3x); - padding-right: var(--tonic-space-3x); - padding-top: var(--tonic-space-2x); + padding-left: var(--tonic-space-4x); + padding-right: var(--tonic-space-4x); + padding-top: calc(.5rem + .125rem); padding-bottom: var(--tonic-space-2x); + border-bottom-color: var(--tonic-colors-transparent); + border-bottom-style: solid; + border-bottom-width: var(--tonic-sizes-1h); +} + +.emotion-8:focus-visible { + outline-color: var(--tonic-colors-blue-60); + outline-width: var(--tonic-sizes-1h); + outline-style: solid; + outline-offset: calc(var(--tonic-space-1h) * -1); + color: var(--tonic-colors-white-disabled); } .emotion-8[aria-selected=true], .emotion-8[data-selected] { - border: none; border-bottom-color: var(--tonic-colors-transparent); border-bottom-style: solid; border-bottom-width: var(--tonic-sizes-1h); color: var(--tonic-colors-white-disabled); - padding-left: calc(.75rem + .125rem); - padding-right: calc(.75rem + .125rem); - padding-top: calc(.5rem + .125rem); -} - -.emotion-8:focus, -.emotion-8[data-focus] { - border-color: var(--tonic-colors-transparent); - color: var(--tonic-colors-white-disabled); } .emotion-8:hover, .emotion-8[data-hover] { - border: none; border-bottom-color: var(--tonic-colors-transparent); border-bottom-style: solid; border-bottom-width: var(--tonic-sizes-1h); color: var(--tonic-colors-white-disabled); - padding-left: calc(.75rem + .125rem); - padding-right: calc(.75rem + .125rem); - padding-top: calc(.5rem + .125rem); } .emotion-10 { diff --git a/packages/react/src/tabs/styles.js b/packages/react/src/tabs/styles.js index 94e04e10a8..186486b5cd 100644 --- a/packages/react/src/tabs/styles.js +++ b/packages/react/src/tabs/styles.js @@ -24,7 +24,7 @@ const useTabStyle = ({ if (variant === 'default') { // border color const disabledBorderColor = 'transparent'; - const focusBorderColor = { + const focusVisibleBorderColor = { dark: 'blue:60', light: 'blue:60', }[colorMode]; @@ -89,61 +89,69 @@ const useTabStyle = ({ return fallbackColor; }; - const selectedPaddingXKey = { - 'horizontal': 'px', - 'vertical': 'pr', - }[orientation]; - const selectedPaddingYKey = { - 'horizontal': 'pt', - 'vertical': 'py', - }[orientation]; - const selectedBorderColorKey = { + const borderColorKey = { 'horizontal': 'borderBottomColor', 'vertical': 'borderLeftColor', }[orientation]; - const selectedBorderStyleKey = { + const borderStyleKey = { 'horizontal': 'borderBottomStyle', 'vertical': 'borderLeftStyle', }[orientation]; - const selectedBorderWidthKey = { + const borderWidthKey = { 'horizontal': 'borderBottomWidth', 'vertical': 'borderLeftWidth', }[orientation]; + const paddingStyle = (() => { + if (orientation === 'horizontal') { + return { + px: '4x', + pt: `calc(${sizes?.['2x']} + ${sizes?.['1h']})`, + pb: '2x', + }; + } + + if (orientation === 'vertical') { + return { + pl: `calc(${sizes?.['4x']} - ${sizes?.['1h']})`, + pr: '4x', + py: `calc(${sizes?.['2x']} + ${sizes?.['1h']})`, + }; + } + + return {}; + })(); + return { fontSize: 'sm', lineHeight: 'sm', - borderColor: 'transparent', - borderStyle: 'solid', - borderWidth: '1h', color: getColorStyleWithFallback(color), cursor: getCursorStyle(), display: 'flex', alignItems: 'center', - px: '3x', - py: '2x', + ...paddingStyle, + [borderColorKey]: 'transparent', + [borderStyleKey]: 'solid', + [borderWidthKey]: '1h', outline: (tabIndex < 0) ? 0 : undefined, // Remove the default outline for tabindex="-1" _hover: { - border: 'none', - [selectedBorderColorKey]: getBorderColorStyleWithFallback(hoverBorderColor), - [selectedBorderStyleKey]: 'solid', - [selectedBorderWidthKey]: '1h', + [borderColorKey]: getBorderColorStyleWithFallback(hoverBorderColor), + [borderStyleKey]: 'solid', + [borderWidthKey]: '1h', color: getColorStyleWithFallback(hoverColor), - [selectedPaddingXKey]: `calc(${sizes?.['3x']} + ${sizes?.['1h']})`, - [selectedPaddingYKey]: `calc(${sizes?.['2x']} + ${sizes?.['1h']})`, }, - _focus: { - borderColor: getBorderColorStyleWithFallback(focusBorderColor), + _focusVisible: { + outlineColor: focusVisibleBorderColor, + outlineWidth: '1h', + outlineStyle: 'solid', + outlineOffset: '-1h', color: getColorStyleWithFallback(focusColor), }, _selected: { - border: 'none', - [selectedBorderColorKey]: getBorderColorStyleWithFallback(selectedBorderColor), - [selectedBorderStyleKey]: 'solid', - [selectedBorderWidthKey]: '1h', + [borderColorKey]: getBorderColorStyleWithFallback(selectedBorderColor), + [borderStyleKey]: 'solid', + [borderWidthKey]: '1h', color: getColorStyleWithFallback(selectedColor), - [selectedPaddingXKey]: `calc(${sizes?.['3x']} + ${sizes?.['1h']})`, - [selectedPaddingYKey]: `calc(${sizes?.['2x']} + ${sizes?.['1h']})`, }, }; } @@ -180,7 +188,7 @@ const useTabStyle = ({ dark: 'gray:80', light: 'gray:20', }[colorMode]; - const focusBorderColor = { + const focusVisibleBorderColor = { dark: 'blue:60', light: 'blue:60', }[colorMode]; @@ -258,10 +266,46 @@ const useTabStyle = ({ return fallbackColor; }; - const siblingMarginKey = { - 'horizontal': 'mr', - 'vertical': 'mb', - }[orientation]; + const marginStyle = (() => { + if (orientation === 'horizontal') { + return { + mr: '-1q', + _lastOfType: { + mr: 0, + }, + }; + } + + if (orientation === 'vertical') { + return { + mb: '-1q', + _lastOfType: { + mb: 0, + }, + }; + } + + return {}; + })(); + + const paddingStyle = (() => { + if (orientation === 'horizontal') { + return { + px: `calc(${sizes?.['4x']} - ${sizes?.['1q']})`, + py: `calc(${sizes?.['10q']} - ${sizes?.['1q']})`, + }; + } + + if (orientation === 'vertical') { + return { + pl: `calc(${sizes?.['4x']} - ${sizes?.['1h']})`, + pr: '4x', + py: `calc(${sizes?.['2x']} + ${sizes?.['1h']})`, + }; + } + + return {}; + })(); return { fontSize: 'sm', @@ -274,22 +318,20 @@ const useTabStyle = ({ cursor: getCursorStyle(), display: 'flex', alignItems: 'center', - px: `calc(${sizes?.['3x']} + ${sizes?.['1q']})`, - py: `calc(${sizes?.['2x']} + ${sizes?.['1q']})`, - [siblingMarginKey]: '-1q', + ...marginStyle, + ...paddingStyle, _hover: { backgroundColor: getBackgroundColorStyleWithFallback(hoverBackgroundColor), borderColor: getBorderColorStyleWithFallback(hoverBorderColor), color: getColorStyleWithFallback(hoverColor), }, - _focus: { + _focusVisible: { backgroundColor: getBackgroundColorStyleWithFallback(focusBackgroundColor), - borderColor: getBorderColorStyleWithFallback(focusBorderColor), - borderStyle: 'solid', - borderWidth: '1h', color: getColorStyleWithFallback(focusColor), - px: '3x', - py: '2x', + outlineColor: focusVisibleBorderColor, + outlineWidth: '1h', + outlineStyle: 'solid', + outlineOffset: '-1h', zIndex: 1, }, _selected: { @@ -297,9 +339,6 @@ const useTabStyle = ({ borderColor: getBorderColorStyleWithFallback(selectedBorderColor), color: getColorStyleWithFallback(selectedColor), }, - _lastOfType: { - [siblingMarginKey]: 0, - }, }; }