Skip to content

Commit

Permalink
Merge pull request #87 from skbkontur/semke/redesign-modals
Browse files Browse the repository at this point in the history
Semke/redesign modals
  • Loading branch information
semkedaniil authored May 21, 2024
2 parents af67d84 + 0b460aa commit c41ac43
Show file tree
Hide file tree
Showing 19 changed files with 307 additions and 265 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v1.8 - 2024.05.21
- Update react-ui
- Add mini modals

## v1.7 - 2023.11.22
- Update yarn to v4
- Update node to v20
Expand Down
2 changes: 1 addition & 1 deletion DbViewer.Tests/FrontTests/BusinessObjectsDownloadTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async Task TestRestrictionsForManyItems()

await businessObjectPage.DownloadLink.Click();
await businessObjectPage.DownloadLimitModal.Header.WaitText("Слишком большой список");
await businessObjectPage.DownloadLimitModal.Body.WaitText("Мы умеем выгружать не более 50000 объектов из этой таблицы. Уточните запрос с помощью фильтров, чтобы записей стало меньше.");
await businessObjectPage.DownloadLimitModal.Body.WaitText("Мы умеем выгружать не более 50000 объектов из этой таблицы.Уточните запрос с помощью фильтров");
await businessObjectPage.DownloadLimitModal.Cancel.Click();

var adminBusinessObjectPage = await (await browser.LoginAsSuperUser()).SwitchTo<BusinessObjectTablePage>("UsersTable");
Expand Down
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ See [DbViewerApplication](https://github.com/skbkontur/db-viewer/blob/master/db-
## How to Start

```
# needed for browser tests
docker pull selenoid/vnc:chrome_112.0
# start databases
docker-compose up -d
Expand Down
3 changes: 1 addition & 2 deletions db-viewer-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@
"dependencies": {
"@skbkontur/react-stack-layout": "^1.0.3",
"@skbkontur/react-ui-validations": "^1.8.3",
"copy-to-clipboard": "^3.3.1",
"date-fns": "^2.29.1",
"lodash": "^4.17.20",
"tslib": "^2.3.0"
},
"devDependencies": {
"@skbkontur/icons": "^1.7.3",
"@skbkontur/react-selenium-testing": "^0.2.1",
"@skbkontur/react-ui": "^4.19.0",
"@skbkontur/react-ui": "^4.22.0",
"@storybook/addon-actions": "^7.6.8",
"@storybook/addons": "^7.6.8",
"@storybook/cli": "^7.6.8",
Expand Down
30 changes: 13 additions & 17 deletions db-viewer-ui/src/Components/AllowCopyToClipboard.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
import { CopyIcon16Regular } from "@skbkontur/icons/CopyIcon16Regular";
import { Link, Toast } from "@skbkontur/react-ui";
import copy from "copy-to-clipboard";
import React, { PropsWithChildren } from "react";

import { StringUtils } from "../Domain/Utils/StringUtils";

export class CopyToClipboardToast {
public static copyText(value: string): void {
copy(value);
navigator.clipboard.writeText(value);
Toast.push("Скопировано в буфер");
}
}

export class AllowCopyToClipboard extends React.Component<PropsWithChildren<{}>> {
public children: null | HTMLSpanElement = null;
export const AllowCopyToClipboard = ({ children }: PropsWithChildren<{}>): React.JSX.Element => {
const childrenRef = React.useRef<HTMLSpanElement | null>(null);

public render(): React.ReactElement {
return (
<span>
<span ref={x => (this.children = x)}>{this.props.children}</span>{" "}
<Link icon={<CopyIcon16Regular />} onClick={this.handleCopy} />
</span>
);
}

private readonly handleCopy = () => {
if (this.children && !StringUtils.isNullOrWhitespace(this.children.innerText)) {
CopyToClipboardToast.copyText(this.children.innerText);
const handleCopy = (): void => {
if (childrenRef.current && !StringUtils.isNullOrWhitespace(childrenRef.current.innerText)) {
CopyToClipboardToast.copyText(childrenRef.current.innerText);
}
};
}
return (
<span>
<span ref={childrenRef}>{children}</span>
<Link icon={<CopyIcon16Regular />} onClick={handleCopy} />
</span>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,24 @@ import { css } from "@skbkontur/react-ui/lib/theming/Emotion";
import { Theme } from "@skbkontur/react-ui/lib/theming/Theme";

export const jsStyles = {
modalText(t: Theme): string {
modalHeader(t: Theme): string {
return css`
font-weight: 700;
color: ${t.textColorDefault};
`;
},

modalBody(t: Theme): string {
return css`
color: ${t.textColorDefault};
font-size: 16px;
`;
},

modalCaption(): string {
return css`
display: inline-block;
margin-bottom: 16px;
`;
},
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Fit, RowStack } from "@skbkontur/react-stack-layout";
import { Button, Modal, ThemeContext } from "@skbkontur/react-ui";
import { XCircleIcon64Regular } from "@skbkontur/icons/icons/XCircleIcon/XCircleIcon64Regular";
import { ColumnStack } from "@skbkontur/react-stack-layout";
import { Button, MiniModal, ThemeContext } from "@skbkontur/react-ui";
import React from "react";

import { jsStyles } from "./ConfirmDeleteObjectModal.styles";
Expand All @@ -12,25 +13,23 @@ interface ConfirmDeleteObjectModalProps {
export function ConfirmDeleteObjectModal({ onDelete, onCancel }: ConfirmDeleteObjectModalProps): React.ReactElement {
const theme = React.useContext(ThemeContext);
return (
<Modal ignoreBackgroundClick noClose data-tid="ConfirmDeleteObjectModal">
<Modal.Header>
<span className={jsStyles.modalText(theme)}>Подтвердите удаление объекта</span>
</Modal.Header>
<Modal.Body>
<span className={jsStyles.modalText(theme)}>Данные об объекте будут удалены безвозвратно</span>
</Modal.Body>
<Modal.Footer panel>
<RowStack gap={2} block>
<Fit>
<Button use="primary" onClick={onDelete} data-tid="Delete">
Удалить
</Button>
</Fit>
<Button onClick={onCancel} data-tid="Cancel">
<MiniModal ignoreBackgroundClick data-tid="ConfirmDeleteObjectModal">
<MiniModal.Header icon={<XCircleIcon64Regular />}>
<span className={jsStyles.modalHeader(theme)}>Удалить объект?</span>
</MiniModal.Header>
<MiniModal.Body>
<span className={jsStyles.modalBody(theme)}>Данные об объекте будут удалены безвозвратно</span>
</MiniModal.Body>
<MiniModal.Footer>
<ColumnStack gap={2} block>
<Button size="medium" use="danger" onClick={onDelete} data-tid="Delete">
Удалить
</Button>
<Button size="medium" onClick={onCancel} data-tid="Cancel">
Отменить
</Button>
</RowStack>
</Modal.Footer>
</Modal>
</ColumnStack>
</MiniModal.Footer>
</MiniModal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ export const jsStyles = {
`;
},

modalText(t: Theme): string {
modalHeader(t: Theme): string {
return css`
font-weight: 700;
color: ${t.textColorDefault};
`;
},

modalBody(t: Theme): string {
return css`
color: ${t.textColorDefault};
font-size: 16px;
`;
},

header(): string {
return css`
margin: 0;
Expand All @@ -25,19 +33,9 @@ export const jsStyles = {
`;
},

content(): string {
return css`
& > p {
margin: 0;
}
`;
},

userMessage(): string {
return css`
& > p {
margin-bottom: ${baseSize}px;
}
margin: 0 0 ${baseSize}px 0;
`;
},

Expand Down
Original file line number Diff line number Diff line change
@@ -1,152 +1,23 @@
import { CopyIcon16Regular } from "@skbkontur/icons/CopyIcon16Regular";
import { Fit, RowStack } from "@skbkontur/react-stack-layout";
import { Button, Link, Modal, ThemeContext } from "@skbkontur/react-ui";
import { Theme } from "@skbkontur/react-ui/lib/theming/Theme";
import React from "react";

import { CopyToClipboardToast } from "../AllowCopyToClipboard";

import { jsStyles } from "./ErrorHandlingContainer.styles";

interface ErrorHandlingContainerModalState {
showStack: boolean;
}
import { ErrorMiniModal } from "./ErrorMiniModal";
import { ErrorModal } from "./ErrorModal";
import { useDeveloperMode } from "./useDeveloperMode";

interface ErrorHandlingContainerModalProps {
canClose: boolean;
onClose: () => void;
message: string;
stack: Nullable<string>;
serverStack: Nullable<string>;
}

export class ErrorHandlingContainerModal extends React.Component<
ErrorHandlingContainerModalProps,
ErrorHandlingContainerModalState
> {
public state: ErrorHandlingContainerModalState = { showStack: false };
private theme!: Theme;

public componentDidMount(): void {
window.addEventListener("keypress", this.handleKeyPress);
}

public componentWillUnmount(): void {
window.removeEventListener("keypress", this.handleKeyPress);
}

private handleKeyPress = (e: KeyboardEvent) => {
if (e.key === "h") {
this.setState({ showStack: true });
}
};

private handleCopyStack = () => {
const { stack } = this.props;
this.copyData(stack);
};

private handleCopyServerStack = () => {
const { serverStack } = this.props;
this.copyData(serverStack);
};

public render(): React.ReactElement {
return (
<ThemeContext.Consumer>
{theme => {
this.theme = theme;
return this.renderMain();
}}
</ThemeContext.Consumer>
);
}

private renderMain(): React.ReactElement {
const { canClose, message, onClose, serverStack, stack } = this.props;

const { showStack } = this.state;
onClose(): void;
}

return (
<Modal data-tid="ErrorHandlingContainerModal" onClose={canClose ? onClose : undefined} noClose={!canClose}>
<Modal.Header data-tid="Header">
<span className={jsStyles.modalText(this.theme)}>Произошла непредвиденная ошибка</span>
</Modal.Header>
<Modal.Body>
<div className={jsStyles.modalText(this.theme)}>
<div className={jsStyles.userMessage()}>
<div data-tid="CallToActionInErrorMessage">
<div className={jsStyles.content()}>
<p>Попробуйте повторить запрос или обновить страницу через некоторое время.</p>
</div>
</div>
</div>
{showStack && (
<div>
<hr />
<div className={jsStyles.errorMessageWrap()} data-tid="ErrorMessage">
{message}
</div>
</div>
)}
{showStack && (
<div className={jsStyles.stackTraces()}>
{stack && (
<RowStack baseline block gap={2}>
<Fit>
<h4 className={jsStyles.header()}>Client stack trace</h4>
</Fit>
<Fit>
<Button icon={<CopyIcon16Regular />} onClick={this.handleCopyStack}>
Скопировать
</Button>
</Fit>
</RowStack>
)}
{stack && (
<div className={jsStyles.stackTraceContainer()}>
<pre data-tid="ClientErrorStack" className={jsStyles.stackTrace(this.theme)}>
{stack}
</pre>
</div>
)}
{serverStack && (
<RowStack baseline block gap={2}>
<Fit>
<h4 className={jsStyles.header()}>Server stack trace</h4>
</Fit>
<Fit>
<Link icon={<CopyIcon16Regular />} onClick={this.handleCopyServerStack}>
Скопировать
</Link>
</Fit>
</RowStack>
)}
{serverStack && (
<div className={jsStyles.stackTraceContainer()}>
<pre data-tid="ServerErrorStack" className={jsStyles.stackTrace(this.theme)}>
{serverStack}
</pre>
</div>
)}
</div>
)}
</div>
</Modal.Body>
{canClose && (
<Modal.Footer panel>
<Button onClick={onClose} data-tid="CloseButton">
Закрыть
</Button>
</Modal.Footer>
)}
</Modal>
);
}
export const ErrorHandlingContainerModal = ({
onClose,
...restProps
}: ErrorHandlingContainerModalProps): React.JSX.Element => {
const showStack = useDeveloperMode();

private copyData(stack: Nullable<string>) {
if (stack != null) {
CopyToClipboardToast.copyText(stack);
}
}
}
return showStack ? <ErrorModal {...restProps} onClose={onClose} /> : <ErrorMiniModal onClose={onClose} />;
};
Loading

0 comments on commit c41ac43

Please sign in to comment.