Skip to content

Commit

Permalink
fix(Popup): accept SVG as anchor (#2946)
Browse files Browse the repository at this point in the history
  • Loading branch information
JackUait committed Aug 11, 2022
1 parent 8d6bf52 commit a5f7283
Show file tree
Hide file tree
Showing 24 changed files with 191 additions and 77 deletions.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions packages/react-ui/components/Hint/Hint.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@
<Hint text="Подсказка">Базовая</Hint>
```

Пример подсказки с иконкой.

```jsx harmony
<Hint text="Редактирование">
<svg width="16" height="16" viewBox="0 0 16 16">
<path
fillRule="evenodd"
d="M13 12V12.998H8V12H13ZM3 13V11L9 4.99999L11 6.99999L5 13H3ZM13 5L11.5 6.5L9.5 4.5L11 3L13 5Z"
clipRule="evenodd"
/>
</svg>
</Hint>
```

Пример подсказки, всегда всплывающей слева.

```jsx harmony
Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/components/Hint/Hint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export class Hint extends React.PureComponent<HintProps, HintState> implements I
);
}

public getAnchorElement = (): Nullable<HTMLElement> => {
public getAnchorElement = (): Nullable<Element> => {
return this.popupRef.current?.anchorElement;
};

Expand Down
47 changes: 47 additions & 0 deletions packages/react-ui/components/Hint/__stories__/Hint.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,53 @@ SetManualAndOpenedPropOnClick.parameters = {
},
};

export const WithSVGIcon: Story = () => {
return (
<Hint text="hint">
<svg data-tid="icon" width="16" height="16" viewBox="0 0 16 16">
<path d="M9 3H7V7H3V9H7V13H9V9H13V7H9V3Z" />
</svg>
</Hint>
);
};

WithSVGIcon.parameters = {
creevey: {
skip: [
{
in: [
'chromeDark',
'chrome8px',
'firefox8px',
'firefox',
'firefoxFlat8px',
'firefoxDark',
'ie118px',
'ie11',
'ie11Dark',
],
reason: 'internal logic being tested and not something UI related',
},
],
tests: {
async idle() {
await this.expect(await this.takeScreenshot()).to.matchImage('idle');
},
async hover() {
await this.browser
.actions()
.move({
origin: this.browser.findElement({ css: '[data-tid="icon"]' }),
})
.perform();
await delay(1000);

await this.expect(await this.browser.takeScreenshot()).to.matchImage('open');
},
},
},
};

@rootNode
class CustomClassComponent extends React.Component<{}, {}> {
private setRootNode!: TSetRootNode;
Expand Down
54 changes: 36 additions & 18 deletions packages/react-ui/components/Hint/__tests__/Hint-test.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,54 @@
import { mount } from 'enzyme';
import React from 'react';
import React, { useState } from 'react';
import userEvent from '@testing-library/user-event';
import { render, screen } from '@testing-library/react';

import { Hint } from '../Hint';

describe('Hint', () => {
it('render without crash', () => {
const wrapper = mount<Hint>(<Hint text="world">Hello</Hint>);
it('should render without crash', () => {
const hintChildrenText = 'Hello';
render(<Hint text="world">{hintChildrenText}</Hint>);

expect(wrapper).toHaveLength(1);
const hintChildren = screen.getByText(hintChildrenText);
expect(hintChildren).toBeInTheDocument();
});

it('controlled opening only if manual prop passed', () => {
const wrapper = mount<Hint>(
<Hint opened text="world">
it('should not open be controlled manually without `manual` prop passed', () => {
const hintText = 'world';
render(
<Hint opened text={hintText}>
Hello
</Hint>,
);

expect(wrapper.state('opened')).toBe(false);
const hintContent = screen.queryByText(hintText);
expect(hintContent).not.toBeInTheDocument();
});

it('manual opening', () => {
const wrapper = mount<Hint>(
<Hint manual text="world">
Hello
</Hint>,
);
it('should open hint manually', () => {
const hintText = 'world';
const Component = () => {
const [isOpen, setIsOpen] = useState(false);

return (
<>
<Hint opened={isOpen} manual text="world">
Hello
</Hint>
<button onClick={() => setIsOpen(true)}>open manually</button>
</>
);
};

render(<Component />);

expect(wrapper.state('opened')).toBe(false);
const hintContent = screen.queryByText(hintText);
expect(hintContent).not.toBeInTheDocument();

wrapper.setProps({ opened: true });
const openButton = screen.getByRole('button');
userEvent.click(openButton);

expect(wrapper.state('opened')).toBe(true);
const hintContentUpdated = screen.getByText(hintText);
expect(hintContentUpdated).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ export const getScrollSizeParams = (inner: HTMLElement, axis: 'x' | 'y') => {
};

export const getScrollYOffset = (element: HTMLElement, container: HTMLElement) => {
const elementOffset = element.offsetTop;
const elementTopOffset = element.offsetTop;

if (container.scrollTop > elementOffset) {
return elementOffset;
if (container.scrollTop > elementTopOffset) {
return elementTopOffset;
}

const offset = elementOffset + element.scrollHeight - container.offsetHeight;
const offset = elementTopOffset + element.scrollHeight - container.offsetHeight;
if (container.scrollTop < offset) {
return offset;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export class ScrollContainer extends React.Component<ScrollContainerProps> {

/**
* @public
* @param {HTMLElement} element
* @param {Element} element
*/
public scrollTo(element: Nullable<HTMLElement>) {
if (!element || !this.inner) {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export class Toast extends React.Component<ToastProps, ToastState> {
);
}

private setRootRef = (element: Nullable<HTMLElement>) => {
private setRootRef = (element: Nullable<Element>) => {
this.setRootNode(element);
// @ts-ignore
this.rootRef.current = element;
Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ export class Tooltip extends React.PureComponent<TooltipProps, TooltipState> imp
);
}

public getAnchorElement = (): Nullable<HTMLElement> => {
public getAnchorElement = (): Nullable<Element> => {
return this.popupRef.current?.anchorElement;
};

Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/internal/CommonWrapper/CommonWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface CommonProps {
}

interface CommonPropsRootNodeRef {
rootNodeRef?: (instance: Nullable<HTMLElement>) => void;
rootNodeRef?: (instance: Nullable<Element>) => void;
}

export type NotCommonProps<P> = Omit<P, keyof CommonProps>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface DropdownContainerPosition {

export interface DropdownContainerProps {
align?: 'left' | 'right';
getParent: () => Nullable<HTMLElement>;
getParent: () => Nullable<Element>;
children?: React.ReactNode;
disablePortal?: boolean;
offsetY?: number;
Expand Down
18 changes: 15 additions & 3 deletions packages/react-ui/internal/InternalMenu/InternalMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';

import { isHTMLElement } from '../../lib/SSRSafe';
import { isNonNullable, isNullable } from '../../lib/utils';
import { isKeyArrowDown, isKeyArrowUp, isKeyEnter } from '../../lib/events/keyboard/identifiers';
import { ScrollContainer, ScrollContainerScrollState } from '../../components/ScrollContainer';
Expand Down Expand Up @@ -222,7 +223,10 @@ export class InternalMenu extends React.PureComponent<MenuProps, MenuState> {
};

private focusOnRootElement = (): void => {
getRootNode(this)?.focus();
const rootNode = getRootNode(this);
if (isHTMLElement(rootNode)) {
rootNode?.focus();
}
};

private shouldRecalculateMaxHeight = (prevProps: MenuProps): boolean => {
Expand Down Expand Up @@ -296,7 +300,11 @@ export class InternalMenu extends React.PureComponent<MenuProps, MenuState> {

private scrollToSelected = () => {
if (this.scrollContainer && this.highlighted) {
this.scrollContainer.scrollTo(getRootNode(this.highlighted));
const rootNode = getRootNode(this.highlighted);
// TODO: Remove this check once IF-647 is resolved
if (rootNode instanceof HTMLElement) {
this.scrollContainer.scrollTo(rootNode);
}
}
};

Expand Down Expand Up @@ -324,7 +332,11 @@ export class InternalMenu extends React.PureComponent<MenuProps, MenuState> {

private highlightItem = (index: number): void => {
this.setState({ highlightedIndex: index });
getRootNode(this)?.focus();

const rootNode = getRootNode(this);
if (isHTMLElement(rootNode)) {
rootNode?.focus();
}
};

private unhighlight = () => {
Expand Down
6 changes: 5 additions & 1 deletion packages/react-ui/internal/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ export class Menu extends React.Component<MenuProps, MenuState> {

private scrollToSelected = () => {
if (this.scrollContainer && this.highlighted) {
this.scrollContainer.scrollTo(getRootNode(this.highlighted));
const rootNode = getRootNode(this.highlighted);
// TODO: Remove this check once IF-647 is resolved
if (rootNode instanceof HTMLElement) {
this.scrollContainer.scrollTo(rootNode);
}
}
};

Expand Down

0 comments on commit a5f7283

Please sign in to comment.