Skip to content

Commit 02ca31d

Browse files
authored
fix(grid): ensure Pane.SplitterButton is rendered as a sibling, rather than child, of Pane.Splitter (#1776)
1 parent 88fe329 commit 02ca31d

File tree

6 files changed

+230
-145
lines changed

6 files changed

+230
-145
lines changed

packages/grid/src/elements/pane/components/Splitter.tsx

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

8-
import React, {
9-
useContext,
10-
useEffect,
11-
forwardRef,
12-
useMemo,
13-
useState,
14-
useRef,
15-
HTMLAttributes
16-
} from 'react';
8+
import React, { useContext, useEffect, forwardRef, useMemo, useRef, HTMLAttributes } from 'react';
179
import mergeRefs from 'react-merge-refs';
1810
import PropTypes from 'prop-types';
1911
import { ThemeContext } from 'styled-components';
20-
import { composeEventHandlers } from '@zendeskgarden/container-utilities';
2112
import { useSplitter } from '@zendeskgarden/container-splitter';
2213
import { usePaneProviderContextData } from '../../../utils/usePaneProviderContext';
2314
import usePaneContext from '../../../utils/usePaneContext';
@@ -43,6 +34,7 @@ const orientationToDimension: Record<string, 'columns' | 'rows'> = {
4334
const SplitterComponent = forwardRef<HTMLDivElement, ISplitterProps>(
4435
(
4536
{
37+
children,
4638
providerId,
4739
layoutKey,
4840
min,
@@ -61,7 +53,6 @@ const SplitterComponent = forwardRef<HTMLDivElement, ISplitterProps>(
6153
const paneContext = usePaneContext();
6254
const themeContext = useContext(ThemeContext);
6355
const environment = useDocument(themeContext);
64-
const [isHovered, setIsHovered] = useState(false);
6556
const isRow = orientationToDimension[orientation!] === 'rows';
6657
const separatorRef = useRef<HTMLDivElement>(null);
6758

@@ -131,14 +122,6 @@ const SplitterComponent = forwardRef<HTMLDivElement, ISplitterProps>(
131122

132123
const size = isRow ? separatorRef.current?.clientWidth : separatorRef.current?.clientHeight;
133124

134-
const onMouseOver = useMemo(
135-
() =>
136-
composeEventHandlers(props.onMouseOver, (event: MouseEvent) =>
137-
setIsHovered(event.target === separatorRef.current)
138-
),
139-
[props.onMouseOver, separatorRef]
140-
);
141-
142125
return (
143126
<PaneSplitterContext.Provider
144127
value={useMemo(
@@ -147,14 +130,13 @@ const SplitterComponent = forwardRef<HTMLDivElement, ISplitterProps>(
147130
)}
148131
>
149132
<StyledPaneSplitter
150-
isHovered={isHovered}
151133
isFixed={isFixed}
152134
orientation={orientation}
153135
{...separatorProps}
154136
{...props}
155-
onMouseOver={onMouseOver}
156137
ref={mergeRefs([separatorRef, ref])}
157138
/>
139+
{children /* Splitter.Button is the only valid child */}
158140
</PaneSplitterContext.Provider>
159141
);
160142
}

packages/grid/src/elements/pane/components/SplitterButton.tsx

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import React, { forwardRef, useCallback } from 'react';
99
import { Tooltip } from '@zendeskgarden/react-tooltips';
1010
import { composeEventHandlers } from '@zendeskgarden/container-utilities';
11-
import { StyledPaneSplitterButton } from '../../../styled';
11+
import { StyledPaneSplitterButton, StyledPaneSplitterButtonContainer } from '../../../styled';
1212
import { ISplitterButtonProps } from '../../../types';
1313
import usePaneSplitterContext from '../../../utils/usePaneSplitterContext';
1414
import { usePaneProviderContextData } from '../../../utils/usePaneProviderContext';
@@ -62,20 +62,29 @@ const SplitterButtonComponent = forwardRef<HTMLButtonElement, ISplitterButtonPro
6262
);
6363

6464
return (
65-
<Tooltip content={label} style={{ cursor: 'default' }} onMouseDown={e => e.stopPropagation()}>
66-
<StyledPaneSplitterButton
67-
aria-label={label}
68-
{...props}
69-
placement={placement!}
70-
orientation={orientation!}
71-
isRotated={isMin}
72-
splitterSize={size || 0}
73-
ref={ref}
74-
onClick={onClick}
75-
onKeyDown={onKeyDown}
76-
onMouseDown={onMouseDown}
77-
/>
78-
</Tooltip>
65+
<StyledPaneSplitterButtonContainer
66+
orientation={orientation!}
67+
placement={placement!}
68+
splitterSize={size || 0}
69+
>
70+
<Tooltip
71+
content={label}
72+
zIndex={2}
73+
style={{ cursor: 'default' }}
74+
onMouseDown={e => e.stopPropagation()}
75+
>
76+
<StyledPaneSplitterButton
77+
aria-label={label}
78+
{...props}
79+
orientation={orientation!}
80+
isRotated={isMin}
81+
ref={ref}
82+
onClick={onClick}
83+
onKeyDown={onKeyDown}
84+
onMouseDown={onMouseDown}
85+
/>
86+
</Tooltip>
87+
</StyledPaneSplitterButtonContainer>
7988
);
8089
}
8190
);

packages/grid/src/styled/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export * from './pane/StyledPane';
1313
export * from './pane/StyledPaneContent';
1414
export * from './pane/StyledPaneSplitter';
1515
export * from './pane/StyledPaneSplitterButton';
16+
export * from './pane/StyledPaneSplitterButtonContainer';

packages/grid/src/styled/pane/StyledPaneSplitter.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { Orientation } from '../../types';
1919
const COMPONENT_ID = 'pane.splitter';
2020

2121
interface IStyledPaneSplitterProps {
22-
isHovered: boolean;
2322
orientation?: Orientation;
2423
isFixed?: boolean;
2524
}
@@ -35,7 +34,7 @@ const colorStyles = (props: IStyledPaneSplitterProps & ThemeProps<DefaultTheme>)
3534
}
3635
3736
&:hover::before {
38-
background-color: ${props.isHovered && hoverColor};
37+
background-color: ${hoverColor};
3938
}
4039
4140
${focusStyles({
@@ -48,7 +47,7 @@ const colorStyles = (props: IStyledPaneSplitterProps & ThemeProps<DefaultTheme>)
4847
})}
4948
5049
&:active::before {
51-
background-color: ${props.isHovered && activeColor};
50+
background-color: ${activeColor};
5251
}
5352
`;
5453
};
@@ -140,7 +139,7 @@ const sizeStyles = (props: IStyledPaneSplitterProps & ThemeProps<DefaultTheme>)
140139
}
141140
142141
&:hover::before {
143-
${dimensionProperty}: ${props.isHovered && separatorSize};
142+
${dimensionProperty}: ${separatorSize};
144143
}
145144
146145
&:focus::before,

packages/grid/src/styled/pane/StyledPaneSplitterButton.ts

Lines changed: 16 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,29 @@
66
*/
77

88
import styled, { css, ThemeProps, DefaultTheme } from 'styled-components';
9-
import { math, stripUnit } from 'polished';
10-
import {
11-
retrieveComponentStyles,
12-
DEFAULT_THEME,
13-
getColorV8,
14-
focusStyles,
15-
SELECTOR_FOCUS_VISIBLE
16-
} from '@zendeskgarden/react-theming';
17-
import { ISplitterButtonProps, Orientation, PLACEMENT } from '../../types';
9+
import { retrieveComponentStyles, DEFAULT_THEME } from '@zendeskgarden/react-theming';
1810
import { ChevronButton } from '@zendeskgarden/react-buttons';
19-
import { StyledPaneSplitter } from './StyledPaneSplitter';
11+
import { Orientation } from '../../types';
2012

2113
const COMPONENT_ID = 'pane.splitter_button';
2214

23-
interface IStyledSplitterButtonProps extends ISplitterButtonProps {
15+
interface IStyledSplitterButtonProps {
2416
orientation: Orientation;
25-
placement: (typeof PLACEMENT)[number];
2617
isRotated: boolean;
27-
splitterSize: number;
2818
}
2919

20+
export const getSize = (theme: DefaultTheme) => theme.space.base * 6;
21+
22+
const sizeStyles = ({ theme }: ThemeProps<DefaultTheme>) => {
23+
const size = `${getSize(theme)}px`;
24+
25+
return css`
26+
width: ${size};
27+
min-width: ${size};
28+
height: ${size};
29+
`;
30+
};
31+
3032
const transformStyles = (props: IStyledSplitterButtonProps & ThemeProps<DefaultTheme>) => {
3133
let degrees = 0;
3234

@@ -49,107 +51,16 @@ const transformStyles = (props: IStyledSplitterButtonProps & ThemeProps<DefaultT
4951
`;
5052
};
5153

52-
const colorStyles = ({ theme }: IStyledSplitterButtonProps & ThemeProps<DefaultTheme>) => {
53-
const boxShadow = theme.shadows.lg(
54-
`${theme.space.base}px`,
55-
`${theme.space.base * 2}px`,
56-
getColorV8('chromeHue', 600, theme, 0.15)!
57-
);
58-
59-
return css`
60-
box-shadow: ${boxShadow};
61-
62-
${focusStyles({
63-
theme,
64-
boxShadow
65-
})}
66-
`;
67-
};
68-
69-
const sizeStyles = (props: IStyledSplitterButtonProps & ThemeProps<DefaultTheme>) => {
70-
const size = `${props.theme.space.base * 6}px`;
71-
const display =
72-
props.splitterSize <=
73-
(stripUnit(math(`${props.theme.shadowWidths.md} * 2 + ${size}`)) as number) && 'none';
74-
const isVertical = props.orientation === 'start' || props.orientation === 'end';
75-
let top;
76-
let left;
77-
let right;
78-
let bottom;
79-
80-
if (props.splitterSize >= (stripUnit(math(`${size} * 3`)) as number)) {
81-
if (props.placement === 'start') {
82-
if (isVertical) {
83-
top = size;
84-
} else if (props.theme.rtl) {
85-
right = size;
86-
} else {
87-
left = size;
88-
}
89-
} else if (props.placement === 'end') {
90-
if (isVertical) {
91-
bottom = size;
92-
} else if (props.theme.rtl) {
93-
left = size;
94-
} else {
95-
right = size;
96-
}
97-
}
98-
}
99-
100-
return css`
101-
display: ${display};
102-
/* stylelint-disable declaration-block-no-redundant-longhand-properties */
103-
top: ${top};
104-
right: ${right};
105-
bottom: ${bottom};
106-
left: ${left};
107-
width: ${size};
108-
min-width: ${size};
109-
height: ${size};
110-
`;
111-
};
112-
113-
/**
114-
* 1. Opaque "dish" behind transparent button
115-
*/
11654
export const StyledPaneSplitterButton = styled(ChevronButton).attrs<IStyledSplitterButtonProps>({
11755
'data-garden-id': COMPONENT_ID,
11856
'data-garden-version': PACKAGE_VERSION,
11957
isBasic: true,
12058
isPill: true,
12159
size: 'small'
12260
})<IStyledSplitterButtonProps>`
123-
position: absolute;
124-
/* prettier-ignore */
125-
transition:
126-
box-shadow 0.1s ease-in-out,
127-
background-color 0.25s ease-in-out,
128-
opacity 0.25s ease-in-out 0.1s;
129-
opacity: 0;
130-
13161
${sizeStyles};
132-
${transformStyles};
133-
134-
/* [1] */
135-
&::before {
136-
position: absolute;
137-
z-index: -1;
138-
background-color: ${props => getColorV8('background', 600 /* default shade */, props.theme)};
139-
width: 100%;
140-
height: 100%;
141-
content: '';
142-
}
143-
144-
${colorStyles};
14562
146-
/* stylelint-disable selector-no-qualifying-type */
147-
${StyledPaneSplitter}:hover &,
148-
${StyledPaneSplitter}:focus-visible &,
149-
${StyledPaneSplitter}[data-garden-focus-visible] &,
150-
${SELECTOR_FOCUS_VISIBLE} {
151-
opacity: 1;
152-
}
63+
${transformStyles};
15364
15465
${props => retrieveComponentStyles(COMPONENT_ID, props)};
15566
`;

0 commit comments

Comments
 (0)