Skip to content

Commit 725cfb3

Browse files
authored
fix(tabs): prevent isVertical styling cascade (#1830)
1 parent 3a7813b commit 725cfb3

File tree

9 files changed

+153
-101
lines changed

9 files changed

+153
-101
lines changed

packages/tabs/src/elements/Tab.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@ export const Tab = React.forwardRef<HTMLDivElement, ITabProps>(
2020
const tabsPropGetters = useTabsContext();
2121

2222
if (disabled || !tabsPropGetters) {
23-
return <StyledTab role="tab" aria-disabled={disabled} ref={ref} {...otherProps} />;
23+
return (
24+
<StyledTab
25+
role="tab"
26+
aria-disabled={disabled}
27+
ref={ref}
28+
isVertical={tabsPropGetters?.isVertical}
29+
{...otherProps}
30+
/>
31+
);
2432
}
2533

2634
const { ref: tabRef, ...tabProps } = tabsPropGetters.getTabProps<HTMLDivElement>({
@@ -30,6 +38,7 @@ export const Tab = React.forwardRef<HTMLDivElement, ITabProps>(
3038
return (
3139
<StyledTab
3240
isSelected={item === tabsPropGetters.selectedValue}
41+
isVertical={tabsPropGetters.isVertical}
3342
{...tabProps}
3443
{...otherProps}
3544
ref={mergeRefs([tabRef, ref])}

packages/tabs/src/elements/TabList.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ export const TabList = React.forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEl
2323
const tabListProps =
2424
tabsPropGetters.getTabListProps<HTMLDivElement>() as HTMLAttributes<HTMLDivElement>;
2525

26-
return <StyledTabList {...tabListProps} {...props} ref={ref} />;
26+
return (
27+
<StyledTabList
28+
isVertical={tabsPropGetters.isVertical}
29+
{...tabListProps}
30+
{...props}
31+
ref={ref}
32+
/>
33+
);
2734
}
2835
);
2936

packages/tabs/src/elements/TabPanel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const TabPanel = React.forwardRef<HTMLDivElement, ITabPanelProps>(
2929
return (
3030
<StyledTabPanel
3131
aria-hidden={tabsPropGetters.selectedValue !== item}
32+
isVertical={tabsPropGetters.isVertical}
3233
{...tabPanelProps}
3334
{...otherProps}
3435
ref={ref}

packages/tabs/src/elements/Tabs.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,13 @@ export const Tabs = forwardRef<HTMLDivElement, ITabsProps>(
4545
}
4646
});
4747

48+
const contextValue = useMemo(
49+
() => ({ isVertical, ...tabsContextValue }),
50+
[isVertical, tabsContextValue]
51+
);
52+
4853
return (
49-
<TabsContext.Provider value={tabsContextValue}>
54+
<TabsContext.Provider value={contextValue}>
5055
<StyledTabs isVertical={isVertical} {...otherProps} ref={ref}>
5156
{children}
5257
</StyledTabs>

packages/tabs/src/styled/StyledTab.ts

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,20 @@ const COMPONENT_ID = 'tabs.tab';
1818

1919
interface IStyledTabProps {
2020
isSelected?: boolean;
21+
isVertical?: boolean;
2122
}
2223

23-
/**
24-
* 1. A high specificity is needed to apply the border-color in vertical orientations
25-
*/
26-
const colorStyles = ({ theme, isSelected }: IStyledTabProps & ThemeProps<DefaultTheme>) => {
24+
const colorStyles = ({
25+
theme,
26+
isSelected,
27+
isVertical
28+
}: IStyledTabProps & ThemeProps<DefaultTheme>) => {
29+
const borderColor = isSelected ? 'currentcolor' : 'transparent';
2730
const selectedColor = getColorV8('primaryHue', 600, theme);
2831

2932
return css`
30-
border-color: ${isSelected && 'currentcolor !important'}; /* [1] */
33+
border-bottom-color: ${isVertical ? undefined : borderColor};
34+
border-${theme.rtl ? 'right' : 'left'}-color: ${isVertical ? borderColor : undefined};
3135
color: ${isSelected ? selectedColor : 'inherit'};
3236
3337
&:hover {
@@ -55,43 +59,73 @@ const colorStyles = ({ theme, isSelected }: IStyledTabProps & ThemeProps<Default
5559
`;
5660
};
5761

58-
const sizeStyles = ({ theme }: ThemeProps<DefaultTheme>) => {
59-
const paddingTop = theme.space.base * 2.5;
60-
const paddingHorizontal = theme.space.base * 7;
61-
const paddingBottom =
62-
paddingTop -
63-
(stripUnit(theme.borderWidths.md) as number) -
64-
(stripUnit(theme.borderWidths.sm) as number);
62+
const sizeStyles = ({ theme, isVertical }: IStyledTabProps & ThemeProps<DefaultTheme>) => {
63+
const borderWidth = theme.borderWidths.md;
64+
const focusHeight = `${theme.space.base * 5}px`;
65+
let marginBottom;
66+
let padding;
67+
68+
if (isVertical) {
69+
marginBottom = `${theme.space.base * 5}px`;
70+
padding = `${theme.space.base}px ${theme.space.base * 2}px`;
71+
} else {
72+
const paddingTop = theme.space.base * 2.5;
73+
const paddingHorizontal = theme.space.base * 7;
74+
const paddingBottom =
75+
paddingTop -
76+
(stripUnit(theme.borderWidths.md) as number) -
77+
(stripUnit(theme.borderWidths.sm) as number);
78+
79+
padding = `${paddingTop}px ${paddingHorizontal}px ${paddingBottom}px`;
80+
}
6581

6682
return css`
67-
padding: ${paddingTop}px ${paddingHorizontal}px ${paddingBottom}px;
83+
margin-bottom: ${marginBottom};
84+
border-width: ${borderWidth};
85+
padding: ${padding};
86+
87+
&:focus-visible::before,
88+
&[data-garden-focus-visible]::before {
89+
height: ${focusHeight};
90+
}
91+
92+
&:last-of-type {
93+
margin-bottom: 0;
94+
}
6895
`;
6996
};
7097

71-
/**
98+
/*
7299
* 1. Text truncation (requires `max-width`).
73100
* 2. Overflow compensation.
74101
* 3. Override default anchor styling
75102
*/
76-
export const StyledTab = styled.div.attrs<IStyledTabProps>({
103+
export const StyledTab = styled.div.attrs({
77104
'data-garden-id': COMPONENT_ID,
78105
'data-garden-version': PACKAGE_VERSION
79106
})<IStyledTabProps>`
80-
display: inline-block;
107+
display: ${props => (props.isVertical ? 'block' : 'inline-block')};
81108
position: relative;
82109
transition: color 0.25s ease-in-out;
83-
border-bottom: ${props => props.theme.borderStyles.solid} transparent;
84-
border-width: ${props => props.theme.borderWidths.md};
110+
border-bottom: ${props => (props.isVertical ? undefined : props.theme.borderStyles.solid)};
111+
border-${props => (props.theme.rtl ? 'right' : 'left')}: ${props => (props.isVertical ? props.theme.borderStyles.solid : undefined)};
85112
cursor: pointer;
86113
overflow: hidden; /* [1] */
87114
vertical-align: top; /* [2] */
88115
user-select: none;
89-
text-align: center;
116+
text-align: ${props => {
117+
if (props.isVertical) {
118+
return props.theme.rtl ? 'right' : 'left';
119+
}
120+
121+
return 'center';
122+
}};
90123
text-decoration: none; /* [3] */
91124
text-overflow: ellipsis; /* [1] */
92125
93-
${sizeStyles}
94-
${colorStyles}
126+
${sizeStyles};
127+
128+
${colorStyles};
95129
96130
&:focus {
97131
text-decoration: none;
@@ -105,11 +139,10 @@ export const StyledTab = styled.div.attrs<IStyledTabProps>({
105139
&:focus-visible::before,
106140
&[data-garden-focus-visible]::before {
107141
position: absolute;
108-
top: ${props => props.theme.space.base * 2.5}px;
109-
right: ${props => props.theme.space.base * 6}px;
110-
left: ${props => props.theme.space.base * 6}px;
142+
top: ${props => props.theme.space.base * (props.isVertical ? 1 : 2.5)}px;
143+
right: ${props => props.theme.space.base * (props.isVertical ? 1 : 6)}px;
144+
left: ${props => props.theme.space.base * (props.isVertical ? 1 : 6)}px;
111145
border-radius: ${props => props.theme.borderRadii.md};
112-
height: ${props => props.theme.space.base * 5}px;
113146
pointer-events: none;
114147
}
115148

packages/tabs/src/styled/StyledTabList.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,61 @@
55
* found at http://www.apache.org/licenses/LICENSE-2.0.
66
*/
77

8-
import styled from 'styled-components';
9-
import { retrieveComponentStyles, getColorV8, DEFAULT_THEME } from '@zendeskgarden/react-theming';
8+
import styled, { DefaultTheme, ThemeProps, css } from 'styled-components';
9+
import {
10+
retrieveComponentStyles,
11+
getColorV8,
12+
DEFAULT_THEME,
13+
getLineHeight
14+
} from '@zendeskgarden/react-theming';
1015

1116
const COMPONENT_ID = 'tabs.tablist';
1217

13-
/**
18+
interface IStyledTabListProps {
19+
isVertical?: boolean;
20+
}
21+
22+
const colorStyles = ({ theme }: ThemeProps<DefaultTheme>) => {
23+
const borderColor = getColorV8('neutralHue', 300, theme);
24+
const foregroundColor = getColorV8('neutralHue', 600, theme);
25+
26+
return css`
27+
border-bottom-color: ${borderColor};
28+
color: ${foregroundColor};
29+
`;
30+
};
31+
32+
/*
1433
* 1. List element reset.
1534
*/
35+
const sizeStyles = ({ theme, isVertical }: IStyledTabListProps & ThemeProps<DefaultTheme>) => {
36+
const marginBottom = isVertical ? 0 : `${theme.space.base * 5}px`;
37+
const borderBottom = isVertical ? undefined : theme.borderWidths.sm;
38+
const fontSize = theme.fontSizes.md;
39+
const lineHeight = getLineHeight(theme.space.base * 5, fontSize);
40+
41+
return css`
42+
margin-top: 0; /* [1] */
43+
margin-bottom: ${marginBottom};
44+
border-bottom-width: ${borderBottom};
45+
padding: 0; /* [1] */
46+
line-height: ${lineHeight};
47+
font-size: ${fontSize};
48+
`;
49+
};
50+
1651
export const StyledTabList = styled.div.attrs({
1752
'data-garden-id': COMPONENT_ID,
1853
'data-garden-version': PACKAGE_VERSION
19-
})`
20-
display: block;
21-
margin-top: 0; /* [1] */
22-
margin-bottom: ${props => props.theme.space.base * 5}px;
23-
border-bottom: ${props => props.theme.borderWidths.sm} ${props => props.theme.borderStyles.solid}
24-
${props => getColorV8('neutralHue', 300, props.theme)};
25-
padding: 0; /* [1] */
26-
line-height: ${props => props.theme.space.base * 5}px;
54+
})<IStyledTabListProps>`
55+
display: ${props => (props.isVertical ? 'table-cell' : 'block')};
56+
border-bottom: ${props => (props.isVertical ? 'none' : props.theme.borderStyles.solid)};
57+
vertical-align: ${props => (props.isVertical ? 'top' : undefined)};
2758
white-space: nowrap;
28-
color: ${props => getColorV8('neutralHue', 600, props.theme)};
29-
font-size: ${props => props.theme.fontSizes.md};
59+
60+
${sizeStyles};
61+
62+
${colorStyles};
3063
3164
${props => retrieveComponentStyles(COMPONENT_ID, props)};
3265
`;

packages/tabs/src/styled/StyledTabPanel.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,31 @@
55
* found at http://www.apache.org/licenses/LICENSE-2.0.
66
*/
77

8-
import styled from 'styled-components';
8+
import styled, { DefaultTheme, ThemeProps, css } from 'styled-components';
99
import { retrieveComponentStyles, DEFAULT_THEME } from '@zendeskgarden/react-theming';
1010

1111
const COMPONENT_ID = 'tabs.tabpanel';
1212

13-
/**
14-
* Accepts all `<div>` props
15-
*/
13+
interface IStyledTabPanelProps {
14+
isVertical?: boolean;
15+
}
16+
17+
const sizeStyles = ({ theme, isVertical }: IStyledTabPanelProps & ThemeProps<DefaultTheme>) => {
18+
const margin = isVertical ? `${theme.space.base * 8}px` : undefined;
19+
20+
return css`
21+
margin-${theme.rtl ? 'right' : 'left'}: ${margin};
22+
`;
23+
};
24+
1625
export const StyledTabPanel = styled.div.attrs({
1726
'data-garden-id': COMPONENT_ID,
1827
'data-garden-version': PACKAGE_VERSION
19-
})`
28+
})<IStyledTabPanelProps>`
2029
display: block;
30+
vertical-align: ${props => props.isVertical && 'top'};
31+
32+
${sizeStyles};
2133
2234
&[aria-hidden='true'] {
2335
display: none;

packages/tabs/src/styled/StyledTabs.ts

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,78 +5,26 @@
55
* found at http://www.apache.org/licenses/LICENSE-2.0.
66
*/
77

8-
import styled, { css, ThemeProps, DefaultTheme } from 'styled-components';
8+
import styled from 'styled-components';
99
import { retrieveComponentStyles, DEFAULT_THEME } from '@zendeskgarden/react-theming';
10-
import { StyledTab } from './StyledTab';
11-
import { StyledTabPanel } from './StyledTabPanel';
12-
import { StyledTabList } from './StyledTabList';
1310

1411
const COMPONENT_ID = 'tabs.tabs';
1512

1613
interface IStyledTabsProps {
17-
/**
18-
* Displays vertical TabList styling
19-
*/
2014
isVertical?: boolean;
2115
}
2216

23-
const verticalStyling = ({ theme }: ThemeProps<DefaultTheme>) => {
24-
return css`
25-
display: table;
26-
27-
${StyledTabList} {
28-
display: table-cell;
29-
margin-bottom: 0;
30-
border-bottom: none;
31-
vertical-align: top;
32-
}
33-
34-
${StyledTab} {
35-
display: block;
36-
margin-bottom: ${theme.space.base * 5}px;
37-
margin-left: ${theme.rtl && '0'};
38-
border-left: ${theme.rtl && '0'};
39-
border-bottom-style: none;
40-
/* stylelint-disable property-case, property-no-unknown */
41-
border-${theme.rtl ? 'right' : 'left'}-style: ${theme.borderStyles.solid};
42-
border-${theme.rtl ? 'right' : 'left'}-color: transparent;
43-
/* stylelint-enable property-case, property-no-unknown */
44-
padding: ${theme.space.base}px ${theme.space.base * 2}px;
45-
text-align: ${theme.rtl ? 'right' : 'left'};
46-
47-
&:last-of-type {
48-
margin-bottom: 0;
49-
}
50-
51-
&:focus-visible::before,
52-
&[data-garden-focus-visible]::before {
53-
top: ${theme.space.base}px;
54-
right: ${theme.space.base}px;
55-
left: ${theme.space.base}px;
56-
}
57-
}
58-
59-
${StyledTabPanel} {
60-
/* stylelint-disable-next-line property-no-unknown */
61-
margin-${theme.rtl ? 'right' : 'left'}: ${theme.space.base * 8}px;
62-
vertical-align: top;
63-
}
64-
`;
65-
};
66-
6717
/**
6818
* Accepts all `<div>` props
6919
*/
7020
export const StyledTabs = styled.div.attrs<IStyledTabsProps>({
7121
'data-garden-id': COMPONENT_ID,
7222
'data-garden-version': PACKAGE_VERSION
7323
})<IStyledTabsProps>`
74-
display: block;
24+
display: ${props => (props.isVertical ? 'table' : 'block')};
7525
overflow: hidden;
7626
direction: ${props => props.theme.rtl && 'rtl'};
7727
78-
${props => props.isVertical && verticalStyling(props)};
79-
8028
${props => retrieveComponentStyles(COMPONENT_ID, props)};
8129
`;
8230

0 commit comments

Comments
 (0)