Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/dropdowns.legacy/src/styled/menu/StyledMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export const StyledMenu = styled.ul.attrs<IStyledMenuProps>(props => ({
${props =>
props.hasArrow &&
arrowStyles(getArrowPosition(props.placement), {
size: `${props.theme.space.base * 2}px`,
inset: '1.5px', // More consistent cross-browser positioning with 1.5px
size: `${props.theme.space.base * 1.5}px`,
inset: '1px',
animationModifier: props.isAnimated ? '.is-animated' : undefined
})};

Expand Down
4 changes: 2 additions & 2 deletions packages/dropdowns/src/views/menu/StyledMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const StyledMenu = styled(StyledListbox).attrs({
${props =>
props.arrowPosition &&
arrowStyles(props.arrowPosition, {
size: `${props.theme.space.base * 2}px`,
inset: '1.5px', // More consistent cross-browser positioning with 1.5px
size: `${props.theme.space.base * 1.5}px`,
inset: '1px',
animationModifier: '[data-garden-animate-arrow="true"]'
})};

Expand Down
17 changes: 13 additions & 4 deletions packages/theming/src/utils/arrowStyles.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import React from 'react';
import { render } from 'garden-test-utils';
import styled, { ThemeProps, DefaultTheme } from 'styled-components';
import { math, stripUnit } from 'polished';
import { stripUnit } from 'polished';
import arrowStyles from './arrowStyles';
import { ArrowPosition } from '../types';

Expand All @@ -29,11 +29,20 @@ const StyledDiv = styled.div<IStyledDivProps>`
`;

const getArrowSize = (size = '6px') => {
return `${Math.round(((stripUnit(size) as number) * 2) / Math.sqrt(2))}px`;
const sizeOffset = 2;
const squareSize = ((stripUnit(size) as number) * 2) / Math.sqrt(2) + sizeOffset;
const squareSizeRounded = Math.round(squareSize * 100) / 100;

return `${squareSizeRounded}px`;
};

const getArrowInset = (inset: string, size?: string) => {
return math(`${getArrowSize(size)} / -2 + ${inset}`);
const insetValue = stripUnit(inset) as number;

const defaultInset = 0.3;
const margin = (stripUnit(getArrowSize(size)) as number) / -2;

return `${Math.round((margin + insetValue + defaultInset) * 100) / 100}px`;
};

describe('arrowStyles', () => {
Expand All @@ -55,7 +64,7 @@ describe('arrowStyles', () => {

POSITION.forEach(position => {
const { container } = render(<StyledDiv arrowPosition={position} />);
const value = math(`${getArrowSize()} / -2`);
const value = getArrowInset('0');

expect(container.firstChild).toHaveStyleRule(position, value, { modifier: '::before' });
});
Expand Down
76 changes: 47 additions & 29 deletions packages/theming/src/utils/arrowStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { css, keyframes } from 'styled-components';
import { math, stripUnit } from 'polished';
import { stripUnit } from 'polished';
import { ArrowPosition } from '../types';

type ArrowOptions = {
Expand Down Expand Up @@ -34,43 +34,52 @@ const animationStyles = (position: ArrowPosition, modifier: string) => {
`;
};

const positionStyles = (position: ArrowPosition, size: string, inset: string) => {
const margin = math(`${size} / -2`);
const placement = math(`${margin} + ${inset}`);
let transform;
const positionStyles = (position: ArrowPosition, size: number, inset: number) => {
/** Overlap the arrow with the base element's border.
* This value + rounding have been found to work well regardless of monitor pixel density and browser.
*/
const defaultInset = 0.3;
const margin = size / -2;
const placement = Math.round((margin + inset + defaultInset) * 100) / 100;

const marginPx = `${margin}px`;
const placementPx = `${placement}px`;
const sizePx = `${size}px`;

let positionCss;
let transform;

if (position.startsWith('top')) {
transform = 'rotate(-135deg)';
positionCss = css`
top: ${placement};
right: ${position === 'top-right' && size};
left: ${position === 'top' ? '50%' : position === 'top-left' && size};
margin-left: ${position === 'top' && margin};
top: ${placementPx};
right: ${position === 'top-right' && sizePx};
left: ${position === 'top' ? '50%' : position === 'top-left' && sizePx};
margin-left: ${position === 'top' && marginPx};
`;
} else if (position.startsWith('right')) {
transform = 'rotate(-45deg)';
positionCss = css`
top: ${position === 'right' ? '50%' : position === 'right-top' && size};
right: ${placement};
bottom: ${position === 'right-bottom' && size};
margin-top: ${position === 'right' && margin};
top: ${position === 'right' ? '50%' : position === 'right-top' && sizePx};
right: ${placementPx};
bottom: ${position === 'right-bottom' && sizePx};
margin-top: ${position === 'right' && marginPx};
`;
} else if (position.startsWith('bottom')) {
transform = 'rotate(45deg)';
positionCss = css`
right: ${position === 'bottom-right' && size};
bottom: ${placement};
left: ${position === 'bottom' ? '50%' : position === 'bottom-left' && size};
margin-left: ${position === 'bottom' && margin};
right: ${position === 'bottom-right' && sizePx};
bottom: ${placementPx};
left: ${position === 'bottom' ? '50%' : position === 'bottom-left' && sizePx};
margin-left: ${position === 'bottom' && marginPx};
`;
} else if (position.startsWith('left')) {
transform = 'rotate(135deg)';
positionCss = css`
top: ${position === 'left' ? '50%' : position === 'left-top' && size};
top: ${position === 'left' ? '50%' : position === 'left-top' && sizePx};
bottom: ${size};
left: ${placement};
margin-top: ${position === 'left' && margin};
left: ${placementPx};
margin-top: ${position === 'left' && marginPx};
`;
}

Expand Down Expand Up @@ -120,9 +129,19 @@ const positionStyles = (position: ArrowPosition, size: string, inset: string) =>
* @component
*/
export default function arrowStyles(position: ArrowPosition, options: ArrowOptions = {}) {
const inset = options.inset || '0';
const size = options.size === undefined ? 6 : (stripUnit(options.size) as number);
const squareSize = `${Math.round((size * 2) / Math.sqrt(2))}px`;
const inset = stripUnit(options.inset || '0') as number;
const size = stripUnit(options.size || '6') as number;

/**
* Adjusts the size to account for the overlap between the arrow and the base element.
* This value + rounding have been found to work well regardless of monitor pixel density and browser.
*/
const sizeOffset = 2;

const squareSize = (size * 2) / Math.sqrt(2) + sizeOffset;
const squareSizeRounded = Math.round(squareSize * 100) / 100;
const squareSizePx = `${squareSizeRounded}px`;

const afterOffset = 0;
const beforeOffset = afterOffset + 2;

Expand All @@ -131,7 +150,7 @@ export default function arrowStyles(position: ArrowPosition, options: ArrowOptio
* 2. Apply shared properties to ::before and ::after.
* 3. Display border with inherited border-color
* 4. Clip the outer square forming the arrow border into a triangle so that the
* border merge with the container's.
* border merges with the container's.
* 5. Clip the inner square forming the arrow body into a triangle so that it
* doesn't interfere with container content.
*/
Expand All @@ -144,26 +163,25 @@ export default function arrowStyles(position: ArrowPosition, options: ArrowOptio
position: absolute;
border-width: inherit;
border-style: inherit;
width: ${squareSize};
height: ${squareSize};
background-color: inherit;
width: ${squareSizePx};
height: ${squareSizePx};
content: '';
box-sizing: inherit;
}

&::before {
border-color: inherit; /* [3] */
background-color: transparent;
clip-path: polygon(100% ${beforeOffset}px, ${beforeOffset}px 100%, 100% 100%); /* [4] */
}

&::after {
border-color: transparent;
background-clip: content-box;
background-color: inherit;
clip-path: polygon(100% ${afterOffset}px, ${afterOffset}px 100%, 100% 100%); /* [5] */
}

${positionStyles(position, squareSize, inset)};
${positionStyles(position, squareSizeRounded, inset)};
${options.animationModifier && animationStyles(position, options.animationModifier)};
`;
}
4 changes: 2 additions & 2 deletions packages/tooltips/src/elements/Tooltip.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ describe('Tooltip', () => {
{
color: PALETTE.white,
bgColor: PALETTE.grey[900],
borderColor: 'transparent'
borderColor: PALETTE.grey[900]
}
],
[
Expand All @@ -143,7 +143,7 @@ describe('Tooltip', () => {
{
color: PALETTE.white,
bgColor: PALETTE.grey[700],
borderColor: 'transparent'
borderColor: PALETTE.grey[700]
}
]
])(
Expand Down
41 changes: 15 additions & 26 deletions packages/tooltips/src/styled/StyledTooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ interface IStyledTooltipProps extends Pick<ITooltipProps, 'hasArrow' | 'size' |
const sizeStyles = ({
theme,
size,
type,
placement,
hasArrow
}: IStyledTooltipProps & ThemeProps<DefaultTheme>) => {
Expand Down Expand Up @@ -74,22 +73,16 @@ const sizeStyles = ({
}

let arrowSize;
let arrowInset;

if (hasArrow) {
if (size === 'small' || size === 'medium') {
arrowSize = margin;
arrowInset = type === 'dark' ? '0px' : '1px';
} else {
arrowInset = type === 'dark' ? '0px' : '1px';

if (size === 'large') {
margin = `${theme.space.base * 2}px`;
arrowSize = margin;
} else if (size === 'extra-large') {
margin = `${theme.space.base * 3}px`;
arrowSize = `${theme.space.base * 2.5}px`;
}
} else if (size === 'large') {
margin = `${theme.space.base * 2}px`;
arrowSize = margin;
} else if (size === 'extra-large') {
margin = `${theme.space.base * 3}px`;
arrowSize = `${theme.space.base * 2.5}px`;
}
}

Expand All @@ -104,11 +97,7 @@ const sizeStyles = ({
font-size: ${fontSize};
overflow-wrap: ${overflowWrap};

${hasArrow &&
arrowStyles(getArrowPosition(theme, placement), {
size: arrowSize,
inset: arrowInset
})};
${hasArrow && arrowStyles(getArrowPosition(theme, placement), { size: arrowSize })};

${StyledParagraph} {
margin-top: ${paragraphMarginTop};
Expand All @@ -128,28 +117,28 @@ const colorStyles = ({ theme, type }: IStyledTooltipProps & ThemeProps<DefaultTh
let titleColor;

if (type === 'light') {
backgroundColor = getColor({ theme, variable: 'background.raised' });
borderColor = getColor({ theme, variable: 'border.default' });
boxShadow = theme.shadows.lg(
`${theme.space.base * (theme.colors.base === 'dark' ? 4 : 5)}px`,
`${theme.space.base * (theme.colors.base === 'dark' ? 6 : 7)}px`,
getColor({ variable: 'shadow.medium', theme })
);
backgroundColor = getColor({ theme, variable: 'background.raised' });
color = getColor({ theme, variable: 'foreground.subtle' });
titleColor = getColor({ theme, variable: 'foreground.default' });
} else {
borderColor = 'transparent';
boxShadow = theme.shadows.lg(
`${theme.space.base}px`,
`${theme.space.base * 2}px`,
getColor({ variable: 'shadow.small', theme })
);
backgroundColor = getColor({
theme,
hue: 'neutralHue',
light: { shade: 900 },
dark: { shade: 700 }
});
borderColor = backgroundColor;
boxShadow = theme.shadows.lg(
`${theme.space.base}px`,
`${theme.space.base * 2}px`,
getColor({ variable: 'shadow.small', theme })
);
color = getColor({ theme, hue: 'white' });
}

Expand Down Expand Up @@ -179,7 +168,7 @@ export const StyledTooltip = styled.div.attrs<IStyledTooltipProps>({
text-align: ${props => (props.theme.rtl ? 'right' : 'left')};
font-weight: ${props => props.theme.fontWeights.regular};

${props => sizeStyles(props)};
${sizeStyles};

&[aria-hidden='true'] {
display: none;
Expand Down