Skip to content

Commit

Permalink
Merge pull request #3766 from Sage/FE-3771-tooltip-flip-override
Browse files Browse the repository at this point in the history
feat(tooltip): add support for overriding default flip placements FE-3771
  • Loading branch information
edleeks87 committed Mar 12, 2021
2 parents 8749415 + 2b88b25 commit aae25e9
Show file tree
Hide file tree
Showing 24 changed files with 419 additions and 54 deletions.
1 change: 1 addition & 0 deletions src/__experimental__/components/label/label.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const Label = ({
warning={warning}
info={info}
tooltipPosition={tooltipPositionValue}
tooltipFlipOverrides={["top", "bottom"]}
/>
</IconWrapperStyle>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,12 @@ const SimpleColorPicker = (props) => {
</StyledColorOptions>
)}
</InputGroupContext.Consumer>
{!validationOnLegend && <ValidationIcon {...validationProps} />}
{!validationOnLegend && (
<ValidationIcon
{...validationProps}
tooltipFlipOverrides={["top", "bottom"]}
/>
)}
</StyledContent>
</Fieldset>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const SwitchSlider = (props) => {
warning={warning}
info={info}
size={props.size}
tooltipFlipOverrides={["top", "bottom"]}
/>
)}
</StyledSwitchSlider>
Expand Down
7 changes: 6 additions & 1 deletion src/__internal__/fieldset/fieldset.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ const Fieldset = ({
</legend>
)}
</InputGroupContext.Consumer>
<ValidationIcon error={error} warning={warning} info={info} />
<ValidationIcon
error={error}
warning={warning}
info={info}
tooltipFlipOverrides={["top", "bottom"]}
/>
</StyledLegendContainer>
)}
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ const BaseButtonToggleGroup = (props) => {
>
{children}
</RadioButtonMapper>
{!validationOnLabel && <ValidationIcon {...validationProps} />}
{!validationOnLabel && (
<ValidationIcon
{...validationProps}
tooltipFlipOverrides={["top", "bottom"]}
/>
)}
</ButtonToggleGroupStyle>
</FormField>
</InputGroupBehaviour>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { baseTheme, mintTheme } from "../../style/themes";
import { assertStyleMatch } from "../../__spec_helper__/test-utils";
import { StyledButtonToggleLabel } from "../button-toggle/button-toggle.style";
import StyledValidationIcon from "../validations/validation-icon.style";
import ValidationIcon from "../validations";
import Label from "../../__experimental__/components/label";

import ButtonToggleGroup from "./button-toggle-group.component";
Expand Down Expand Up @@ -78,6 +79,7 @@ describe("ButtonToggleGroup", () => {
expect(wrapper).toMatchSnapshot();
});
});

describe("Style props", () => {
it("renders with the correct width", () => {
const wrapper = renderWithTheme(
Expand Down Expand Up @@ -117,6 +119,7 @@ describe("ButtonToggleGroup", () => {
{ modifier: `${StyledButtonToggleLabel}` }
);
});

it("renders validation icon on input", () => {
const wrapper = renderWithTheme(
{ theme: baseTheme, [type]: "Message" },
Expand All @@ -128,7 +131,12 @@ describe("ButtonToggleGroup", () => {
.find(StyledValidationIcon)
.exists()
).toBe(true);

expect(
wrapper.find(ValidationIcon).props().tooltipFlipOverrides
).toEqual(["top", "bottom"]);
});

it("renders validation icon on label when validationOnLabel passed as true", () => {
const wrapper = renderWithTheme(
{
Expand Down
18 changes: 18 additions & 0 deletions src/components/help/help.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const Help = (props) => {
type,
tooltipBgColor,
tooltipFontColor,
tooltipFlipOverrides,
} = props;

useEffect(() => {
Expand Down Expand Up @@ -76,6 +77,7 @@ const Help = (props) => {
tooltipVisible={isFocused || isTooltipVisible}
tooltipBgColor={tooltipBgColor}
tooltipFontColor={tooltipFontColor}
tooltipFlipOverrides={tooltipFlipOverrides}
/>
</StyledHelp>
);
Expand Down Expand Up @@ -104,6 +106,22 @@ Help.propTypes = {
tooltipBgColor: PropTypes.string,
/** Override font color of the Tooltip, provide any color from palette or any valid css color value. */
tooltipFontColor: PropTypes.string,
/** Overrides the default flip behaviour of the Tooltip, must be an array containing some or all of ["top", "bottom", "left", "right"] (see https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements) */
tooltipFlipOverrides: (props, propName, componentName) => {
const prop = props[propName];
const isValid =
prop &&
Array.isArray(prop) &&
prop.every((placement) => OptionsHelper.positions.includes(placement));

if (!prop || isValid) {
return null;
}
return new Error(
// eslint-disable-next-line max-len
`The \`${propName}\` prop supplied to \`${componentName}\` must be an array containing some or all of ["top", "bottom", "left", "right"].`
);
},
};

Help.defaultProps = {
Expand Down
5 changes: 3 additions & 2 deletions src/components/help/help.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { IconTypes } from "../../utils/helpers/options-helper/options-helper";
import { IconTypes, Positions } from "../../utils/helpers/options-helper/options-helper";

export interface HelpProps {
className?: string;
Expand All @@ -10,9 +10,10 @@ export interface HelpProps {
href?: string;
isFocused?: boolean;
type?: IconTypes;
tooltipPosition?: "top" | "bottom" | "left" | "right";
tooltipPosition?: Positions;
tooltipBgColor?: string;
tooltipFontColor?: string;
tooltipFlipOverrides?: Positions[];
}

declare const Help: React.ComponentType<HelpProps>;
Expand Down
25 changes: 25 additions & 0 deletions src/components/help/help.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,31 @@ describe("Help", () => {
document.body.removeChild(domNode);
});
});

describe("tooltipFlipOverrides", () => {
it("does not throw an error if a valid array is passed", () => {
jest.spyOn(global.console, "error").mockImplementation(() => {});

renderHelp(
{ type: "home", tooltipFlipOverrides: ["top", "bottom"] },
mount
);

// eslint-disable-next-line no-console
expect(console.error).not.toHaveBeenCalled();
global.console.error.mockReset();
});

it("throws an error if a invalid array is passed", () => {
jest.spyOn(global.console, "error").mockImplementation(() => {});

renderHelp({ type: "home", tooltipFlipOverrides: ["foo", "bar"] }, mount);

// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalled();
global.console.error.mockReset();
});
});
});

function renderHelp(props, renderer = shallow) {
Expand Down
31 changes: 27 additions & 4 deletions src/components/help/help.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { text, select } from "@storybook/addon-knobs";
import { text, select, boolean } from "@storybook/addon-knobs";
import OptionsHelper from "../../utils/helpers/options-helper";
import Help from "./help.component";

Expand Down Expand Up @@ -28,17 +28,40 @@ export const Default = () => {
: undefined;
const href = text("href", "http://www.sage.com");
const type = select("type", OptionsHelper.icons, "help");
const tooltipBgColor = text("tooltipBgColor", undefined);
const tooltipFontColor = text("tooltipFontColor", undefined);
const tooltipBgColor = children
? text("tooltipBgColor", undefined)
: undefined;
const tooltipFontColor = children
? text("tooltipFontColor", undefined)
: undefined;

const isVertical = ["top", "bottom"].includes(tooltipPosition);
const enableFlipOverrides = children
? boolean("enable flip overrides", false)
: undefined;

const tooltipFlipOverrides =
children && enableFlipOverrides
? select(
"tooltipFlipOverrides",
isVertical ? ["left", "right"] : ["top", "bottom"],
isVertical ? "right" : "bottom"
)
: undefined;

const flipOverrides = tooltipFlipOverrides
? [tooltipFlipOverrides]
: undefined;

return (
<div style={{ marginLeft: "125px" }}>
<div style={{ margin: "200px" }}>
<Help
tooltipPosition={tooltipPosition}
href={href}
type={type}
tooltipBgColor={tooltipBgColor}
tooltipFontColor={tooltipFontColor}
tooltipFlipOverrides={flipOverrides}
>
{children}
</Help>
Expand Down
22 changes: 21 additions & 1 deletion src/components/icon/icon.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const Icon = React.forwardRef(
tooltipVisible,
tooltipBgColor,
tooltipFontColor,
tooltipFlipOverrides,
tabIndex,
isPartOfInput,
inputSize,
Expand Down Expand Up @@ -90,6 +91,7 @@ const Icon = React.forwardRef(
inputSize={inputSize}
bgColor={tooltipBgColor}
fontColor={tooltipFontColor}
flipOverrides={tooltipFlipOverrides}
>
{icon}
</Tooltip>
Expand All @@ -100,6 +102,8 @@ const Icon = React.forwardRef(
}
);

const placements = ["top", "bottom", "left", "right"];

Icon.propTypes = {
/**
* @private
Expand Down Expand Up @@ -146,13 +150,29 @@ Icon.propTypes = {
/** The message string to be displayed in the tooltip */
tooltipMessage: PropTypes.string,
/** The position to display the tooltip */
tooltipPosition: PropTypes.oneOf(["top", "bottom", "left", "right"]),
tooltipPosition: PropTypes.oneOf(placements),
/** Control whether the tooltip is visible */
tooltipVisible: PropTypes.bool,
/** Override background color of the Tooltip, provide any color from palette or any valid css color value. */
tooltipBgColor: PropTypes.string,
/** Override font color of the Tooltip, provide any color from palette or any valid css color value. */
tooltipFontColor: PropTypes.string,
/** Overrides the default flip behaviour of the Tooltip, must be an array containing some or all of ["top", "bottom", "left", "right"] (see https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements) */
tooltipFlipOverrides: (props, propName) => {
const prop = props[propName];
const isValid =
prop &&
Array.isArray(prop) &&
prop.every((placement) => placements.includes(placement));

if (!prop || isValid) {
return null;
}
return new Error(
// eslint-disable-next-line max-len
`The \`${propName}\` prop supplied to \`Icon\` must be an array containing some or all of ["top", "bottom", "left", "right"].`
);
},
/** @ignore @private */
isPartOfInput: PropTypes.bool,
/** @ignore @private */
Expand Down
6 changes: 4 additions & 2 deletions src/components/icon/icon.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';

import { Positions } from "../../utils/helpers/options-helper";
export interface IconProps {
/** Icon type */
type: string;
Expand Down Expand Up @@ -28,13 +28,15 @@ export interface IconProps {
/** The message string to be displayed in the tooltip */
tooltipMessage?: string;
/** The position to display the tooltip */
tooltipPosition?: "top" | "bottom" | "left" | "right";
tooltipPosition?: Positions;
/** Control whether the tooltip is visible */
tooltipVisible?: boolean;
/** Override background color of the Tooltip, provide any color from palette or any valid css color value. */
tooltipBgColor?: string;
/** Override font color of the Tooltip, provide any color from palette or any valid css color value. */
tooltipFontColor?: string;
/** Overrides the default flip behaviour of the Tooltip */
tooltipFlipOverrides?: Positions[];
}

declare const Icon: React.ComponentType<IconProps>;
Expand Down
54 changes: 52 additions & 2 deletions src/components/icon/icon.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,31 @@ describe("Icon component", () => {

const wrongColors = ["rgb(0,0)", "#ff", "test"];
describe.each(wrongColors)("when wrong color prop is provided", (color) => {
beforeEach(() => {
jest.spyOn(global.console, "error").mockImplementation(() => {});
});

afterEach(() => {
global.console.error.mockReset();
});

it("throws an error", () => {
jest.spyOn(global.console, "error");
mount(<Icon color={color} type="message" />);
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalled();
});
});

describe.each(wrongColors)("when wrong bg prop is provided", (color) => {
beforeEach(() => {
jest.spyOn(global.console, "error").mockImplementation(() => {});
});

afterEach(() => {
global.console.error.mockReset();
});

it("throws an error", () => {
jest.spyOn(global.console, "error");
mount(<Icon bg={color} type="message" />);
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalled();
Expand Down Expand Up @@ -499,5 +514,40 @@ describe("Icon component", () => {

expect(wrapper.find(Tooltip).props().isVisible).toEqual(false);
});

describe("tooltipFlipOverrides", () => {
it("does not throw an error if a valid array is passed", () => {
global.console.error.mockReset();

jest.spyOn(global.console, "error").mockImplementation(() => {});

mount(
<Icon
type="home"
tooltipMessage="foo"
tooltipFlipOverrides={["top", "bottom"]}
/>
);

// eslint-disable-next-line no-console
expect(console.error).not.toHaveBeenCalled();
global.console.error.mockReset();
});

it("throws an error if a invalid array is passed", () => {
jest.spyOn(global.console, "error").mockImplementation(() => {});
mount(
<Icon
type="home"
tooltipMessage="foo"
tooltipFlipOverrides={["foo", "bar"]}
/>
);

// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalled();
global.console.error.mockReset();
});
});
});
});
Loading

0 comments on commit aae25e9

Please sign in to comment.