Skip to content

Commit

Permalink
fix: add fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
mshatikhin committed Nov 14, 2024
1 parent cb40b44 commit 957e78b
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 64 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 24 additions & 3 deletions packages/react-ui/components/PasswordInput/PasswordInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface PasswordInputProps extends Pick<AriaAttributes, 'aria-label'>,

export interface PasswordInputState {
visible: boolean;
focused: boolean;
capsLockEnabled?: boolean | null;
}

Expand Down Expand Up @@ -63,6 +64,7 @@ export class PasswordInput extends React.PureComponent<PasswordInputProps, Passw

public state: PasswordInputState = {
visible: false,
focused: false,
capsLockEnabled: false,
};

Expand Down Expand Up @@ -170,15 +172,27 @@ export class PasswordInput extends React.PureComponent<PasswordInputProps, Passw
};

private handleToggleVisibility = () => {
this.setState((prevState) => ({ visible: !prevState.visible }), this.handleFocus);
this.setState((prevState) => ({ visible: !prevState.visible }), this.handleFocusOnInput);
};

private handleFocus = () => {
private handleFocusOnInput = () => {
if (this.input) {
this.input.focus();
}
};

private handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
if (this.state.focused) {
return;
}

this.setState({ focused: true });

if (this.props.onFocus) {
this.props.onFocus(event);
}
};

private handleBlur = () => {
if (this.input) {
this.input.blur();
Expand Down Expand Up @@ -229,6 +243,12 @@ export class PasswordInput extends React.PureComponent<PasswordInputProps, Passw

private hideSymbols = () => {
this.setState({ visible: false });

if (!this.state.focused) {
return;
}

this.setState({ focused: false });
};

private renderMain = (props: CommonWrapperRestProps<PasswordInputProps>) => {
Expand All @@ -238,10 +258,11 @@ export class PasswordInput extends React.PureComponent<PasswordInputProps, Passw
onKeyDown: this.handleKeydown,
onKeyPress: this.handleKeyPress,
rightIcon: this.renderEye(),
onFocus: this.handleFocus,
};

return (
<RenderLayer onFocusOutside={this.hideSymbols} onClickOutside={this.hideSymbols}>
<RenderLayer onFocusOutside={this.hideSymbols} onClickOutside={this.hideSymbols} active={this.state.focused}>
<div data-tid={PasswordInputDataTids.root} className={this.styles.root()}>
<Input ref={this.refInput} type={this.state.visible ? 'text' : 'password'} {...inputProps} />
</div>
Expand Down
6 changes: 5 additions & 1 deletion packages/react-ui/components/TokenInput/TokenInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { createPropsGetter } from '../../lib/createPropsGetter';
import { getUid } from '../../lib/uidUtils';
import { TokenView } from '../Token/TokenView';
import { ThemeContext } from '../../lib/theming/ThemeContext';
import { isShadowRoot } from '../../lib/shadowDom/isShadowRoot';

import { TokenInputLocale, TokenInputLocaleHelper } from './locale';
import { getStyles } from './TokenInput.styles';
Expand Down Expand Up @@ -725,7 +726,10 @@ export class TokenInput<T = string> extends React.PureComponent<TokenInputProps<
private isBlurToMenu = (event: FocusEvent<HTMLElement>) => {
if (this.menuRef && globalObject.document) {
const menu = getRootNode(this.tokensInputMenu?.getMenuRef());
const relatedTarget = event.relatedTarget || globalObject.document.activeElement;

const isShadowRootElement = isShadowRoot(this.emotion.sheet.container.getRootNode());
const relatedTarget =
(isShadowRootElement ? event.target : event.relatedTarget) ?? globalObject.document.activeElement;

if (menu && menu.contains(relatedTarget)) {
return true;
Expand Down
14 changes: 6 additions & 8 deletions packages/react-ui/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ import { Popup, PopupPositionsType, PopupProps, ShortPopupPositionsType } from '
import { RenderLayer, RenderLayerProps } from '../../internal/RenderLayer';
import { Nullable } from '../../typings/utility-types';
import { MouseEventType } from '../../typings/event-types';
import { containsTargetOrRenderContainer } from '../../lib/listenFocusOutside';
import { clickOutsideContent } from '../../lib/listenFocusOutside';
import { Theme } from '../../lib/theming/Theme';
import { isTestEnv } from '../../lib/currentEnvironment';
import { CommonProps, CommonWrapper } from '../../internal/CommonWrapper';
import { rootNode, TSetRootNode } from '../../lib/rootNode';
import { InstanceWithAnchorElement } from '../../lib/InstanceWithAnchorElement';
import { createPropsGetter } from '../../lib/createPropsGetter';
import { CloseButtonIcon } from '../../internal/CloseButtonIcon/CloseButtonIcon';
import { isInstanceOf } from '../../lib/isInstanceOf';
import { EmotionConsumer } from '../../lib/theming/Emotion';
import { ThemeContext } from '../../lib/theming/ThemeContext';
import {
getFullReactUIFlagsContext,
ReactUIFeatureFlags,
ReactUIFeatureFlagsContext,
} from '../../lib/featureFlagsContext';
import { isShadowRoot } from '../../lib/shadowDom/isShadowRoot';

import { getStyles } from './Tooltip.styles';

Expand Down Expand Up @@ -548,12 +548,10 @@ export class Tooltip extends React.PureComponent<TooltipProps, TooltipState> imp
}
};

private isClickOutsideContent(event: Event) {
if (this.contentElement && isInstanceOf(event.target, globalObject.Element)) {
return !containsTargetOrRenderContainer(event.target)(this.contentElement);
}

return true;
private isClickOutsideContent(event: Event): boolean {
const node = this.contentElement;
const isShadowRootElement = isShadowRoot(this.emotion.sheet.container.getRootNode());
return clickOutsideContent(event, node, isShadowRootElement);
}

private handleFocus = () => {
Expand Down
118 changes: 100 additions & 18 deletions packages/react-ui/components/__stories__/shadowDom.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,32 @@ const Components: React.JSX.Element[] = [
<Button>button</Button>,
<Calendar value={'22.11.2020'} />,
<Center children={'center'} />,
<Checkbox children={'checkbox'} />,
<ComboBox getItems={() => Promise.resolve([1, 2, 3])} />,
<Checkbox children={'checkbox'} onValueChange={console.log} />,
<ComboBox<number> getItems={() => Promise.resolve([1, 2, 3])} renderItem={(x) => x} onValueChange={console.log} />,
<CurrencyInput onValueChange={console.log} />,
<CurrencyLabel value={123} />,
<DateInput />,
<DateInput onValueChange={console.log} />,
<DatePicker onValueChange={console.log} />,
<Dropdown caption={'x'} />,
<DropdownMenu caption={'x'} />,
<FileUploader />,
<Dropdown caption={'Dropdown'}>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
</Dropdown>,
<DropdownMenu caption={<Button>DropdownMenu</Button>}>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
</DropdownMenu>,
<FileUploader onValueChange={console.log} />,
<FxInput onValueChange={console.log} />,
<Gapped gap={0}>gap</Gapped>,
<GlobalLoader />,
<Group>
<Button>button</Button>
<Input />
<Input onValueChange={console.log} />
</Group>,
<Hint text={'hint'}>hint</Hint>,
<Input />,
<Input onValueChange={console.log} />,
<Kebab children={'kebab'} />,
<Link>link</Link>,
// <Loader />,
Expand All @@ -83,11 +91,13 @@ const Components: React.JSX.Element[] = [
// <MiniModal />,
// <Modal />,
<Paging activePage={0} pagesCount={6} onPageChange={console.log} />,
<PasswordInput />,
<Radio value={1} children={'radio'} />,
<RadioGroup items={[1, 2]} />,
<form action="">
<PasswordInput autoComplete={'off'} onValueChange={console.log} />
</form>,
<Radio value={1} children={'radio'} onValueChange={console.log} />,
<RadioGroup items={[1, 2]} onValueChange={console.log} />,
<ScrollContainer maxHeight={100} children={<div style={{ height: 1000, backgroundColor: 'cyan' }}></div>} />,
<Select items={[1, 2]} />,
<Select items={[1, 2]} onValueChange={console.log} />,
// <SidePage />,
// <Spinner />,
<Sticky side={'bottom'} />,
Expand All @@ -96,17 +106,86 @@ const Components: React.JSX.Element[] = [
{ label: '1', value: '1' },
{ label: '2', value: '2' },
]}
onValueChange={console.log}
/>,
<Tabs value={''} />,
<Textarea value={''} />,
<Textarea value={''} onValueChange={console.log} />,
<Toast />,
<SingleToast />,
<Toggle />,
<Toggle onValueChange={console.log} />,
<Token children={'token'} />,
<TokenInput getItems={() => Promise.resolve([1, 2, 3])} />,
<Tooltip>tooltip</Tooltip>,
<TooltipMenu caption={'tooltipMenu'} />,
<MaskedInput mask={'fack'} />,
<TokenInput
getItems={() => Promise.resolve([1, 2, 3])}
valueToString={(x) => x.toString()}
onValueChange={console.log}
/>,
<Tooltip render={() => <div>Tooltip</div>}>tooltip</Tooltip>,
<Tooltip
trigger="click"
render={() => {
return (
<Gapped vertical>
<Dropdown caption={'Dropdown'}>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
</Dropdown>
<DropdownMenu caption={<Button>DropdownMenu</Button>}>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
</DropdownMenu>
<TooltipMenu caption={<Button>TooltipMenu</Button>}>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
</TooltipMenu>
<DatePicker onValueChange={console.log} />
<ComboBox<number> getItems={() => Promise.resolve([1, 2, 3])} renderItem={(x) => x} />
<FileUploader />
<Select items={[1, 2]} />
<TokenInput<number> getItems={() => Promise.resolve([1, 2, 3])} valueToString={(x) => x.toString()} />
<Tooltip
render={() => (
<Gapped vertical>
<Dropdown caption={'Dropdown'}>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
</Dropdown>
<DropdownMenu caption={<Button>DropdownMenu</Button>}>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
</DropdownMenu>
<TooltipMenu caption={<Button>TooltipMenu</Button>}>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
</TooltipMenu>
<DatePicker onValueChange={console.log} />
<ComboBox<number> getItems={() => Promise.resolve([1, 2, 3])} renderItem={(x) => x} />
<FileUploader />
<Select items={[1, 2]} />
<TokenInput<number> getItems={() => Promise.resolve([1, 2, 3])} valueToString={(x) => x.toString()} />
<Tooltip render={() => <div>Tooltip</div>}>tooltip</Tooltip>
</Gapped>
)}
>
tooltip
</Tooltip>
</Gapped>
);
}}
>
<Button>Trip tooltip</Button>
</Tooltip>,
<TooltipMenu caption={<Button>TooltipMenu</Button>}>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
<MenuItem>MenuItem</MenuItem>
</TooltipMenu>,
<MaskedInput mask={'fack'} onValueChange={console.log} />,
<ResponsiveLayout children={(currentLayout) => <div>{currentLayout.isMobile ? 'isMobile' : 'isDesktop'}</div>} />,
];

Expand Down Expand Up @@ -206,6 +285,9 @@ export const ModalScenarios = () => {
<MenuItem>MenuItem</MenuItem>
</Kebab>
<DatePicker onValueChange={console.log} />
<Tooltip render={() => <DatePicker onValueChange={console.log} />}>
<Button>DatepickerForm</Button>
</Tooltip>
</Gapped>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { PropsWithChildren, isValidElement, cloneElement, ReactElement } from 'react';
import type { Nullable } from 'react-ui/typings/utility-types';

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

Expand All @@ -9,7 +10,10 @@ interface Props {
* Использовать только когда на children нет пропса disabled
*/
disabled?: boolean;

/**
* Ссылка на корневой html элемент
*/
rootNodeRef?: (e: Nullable<React.ReactInstance>) => void;
/**
* Событие вызывается когда элемент потеряет фокус, и при этом он задисэйблен
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/internal/InputLikeText/InputLikeText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export class InputLikeText extends React.Component<InputLikeTextProps, InputLike
Object.assign(context, { disabled, focused, size });

return (
<FocusControlWrapper disabled={disabled} onBlurWhenDisabled={this.resetFocus}>
<FocusControlWrapper rootNodeRef={this.setRootNode} disabled={disabled} onBlurWhenDisabled={this.resetFocus}>
<span
data-tid={InputLikeTextDataTids.root}
{...rest}
Expand Down
21 changes: 3 additions & 18 deletions packages/react-ui/internal/RenderLayer/RenderLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import React from 'react';
import { globalObject } from '@skbkontur/global-object';
import { Emotion } from '@emotion/css/types/create-instance';

import { containsTargetOrRenderContainer, listen as listenFocusOutside } from '../../lib/listenFocusOutside';
import { clickOutsideContent, listen as listenFocusOutside } from '../../lib/listenFocusOutside';
import { CommonProps, CommonWrapper } from '../CommonWrapper';
import { getRootNode, rootNode, TSetRootNode } from '../../lib/rootNode';
import { Nullable } from '../../typings/utility-types';
import { createPropsGetter } from '../../lib/createPropsGetter';
import { isInstanceOf } from '../../lib/isInstanceOf';
import { EmotionConsumer } from '../../lib/theming/Emotion';
import { isShadowRoot } from '../../lib/shadowDom/isShadowRoot';

Expand Down Expand Up @@ -129,24 +128,10 @@ export class RenderLayer extends React.Component<RenderLayerProps> {
};

private handleNativeDocClick = (event: Event) => {
const target = event.target || event.srcElement;
const node = this.getAnchorNode();

const isShadowRootElement = isShadowRoot(this.emotion.sheet.container.getRootNode());
if (
!node ||
(isShadowRootElement && event.composed && event.composedPath().indexOf(node) > -1) ||
(isShadowRootElement &&
isInstanceOf(target, globalObject.Element) &&
containsTargetOrRenderContainer(event.composedPath()[0] as unknown as Element)(node)) ||
(!isShadowRootElement &&
isInstanceOf(target, globalObject.Element) &&
containsTargetOrRenderContainer(target)(node))
) {
return;
}

if (this.props.onClickOutside) {
const clickOutsideOfContent = clickOutsideContent(event, node, isShadowRootElement);
if (clickOutsideOfContent && this.props.onClickOutside) {
this.props.onClickOutside(event);
}
};
Expand Down
Loading

0 comments on commit 957e78b

Please sign in to comment.