Add Alert component for showing warnings and errors
This commit is contained in:
81
extensions/ql-vscode/src/stories/common/Alert.stories.tsx
Normal file
81
extensions/ql-vscode/src/stories/common/Alert.stories.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { VSCodeButton, VSCodeLink } from '@vscode/webview-ui-toolkit/react';
|
||||
|
||||
import { VariantAnalysisContainer } from '../../view/variant-analysis/VariantAnalysisContainer';
|
||||
import { Alert } from '../../view/common';
|
||||
|
||||
export default {
|
||||
title: 'Alert',
|
||||
component: Alert,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<VariantAnalysisContainer>
|
||||
<Story />
|
||||
</VariantAnalysisContainer>
|
||||
)
|
||||
],
|
||||
} as ComponentMeta<typeof Alert>;
|
||||
|
||||
const Template: ComponentStory<typeof Alert> = (args) => (
|
||||
<Alert {...args} />
|
||||
);
|
||||
|
||||
export const Warning = Template.bind({});
|
||||
Warning.args = {
|
||||
type: 'warning',
|
||||
title: 'This query found a warning',
|
||||
message: <>Warning content with <VSCodeLink>links</VSCodeLink></>,
|
||||
};
|
||||
|
||||
export const WarningInverse = Template.bind({});
|
||||
WarningInverse.args = {
|
||||
...Warning.args,
|
||||
message: 'Warning content',
|
||||
inverse: true,
|
||||
};
|
||||
|
||||
export const WarningExample = Template.bind({});
|
||||
WarningExample.args = {
|
||||
type: 'warning',
|
||||
title: 'Query manually stopped',
|
||||
message: 'This query was manually stopped before the analysis completed. Results may be partial.',
|
||||
};
|
||||
|
||||
export const Error = Template.bind({});
|
||||
Error.args = {
|
||||
type: 'error',
|
||||
title: 'This query found an error',
|
||||
message: <>Error content with <VSCodeLink>links</VSCodeLink></>,
|
||||
};
|
||||
|
||||
export const ErrorInverse = Template.bind({});
|
||||
ErrorInverse.args = {
|
||||
...Error.args,
|
||||
message: 'Error content',
|
||||
inverse: true,
|
||||
};
|
||||
|
||||
export const ErrorExample = Template.bind({});
|
||||
ErrorExample.args = {
|
||||
type: 'error',
|
||||
title: 'Request failed',
|
||||
message: <>
|
||||
Request to https://api.github.com/repos/octodemo/Hello-World/code-scanning/codeql/queries failed.{' '}
|
||||
<VSCodeLink>Check logs</VSCodeLink> and try running this query again.
|
||||
</>,
|
||||
};
|
||||
|
||||
export const ErrorWithButtons = Template.bind({});
|
||||
ErrorWithButtons.args = {
|
||||
type: 'error',
|
||||
title: 'Request failed',
|
||||
message: 'Request to https://api.github.com/repos/octodemo/Hello-World/code-scanning/codeql/queries failed. Try running this query again.',
|
||||
actions: (
|
||||
<>
|
||||
<VSCodeButton appearance="secondary">View logs</VSCodeButton>
|
||||
<VSCodeButton>Retry</VSCodeButton>
|
||||
</>
|
||||
),
|
||||
};
|
||||
98
extensions/ql-vscode/src/view/common/Alert.tsx
Normal file
98
extensions/ql-vscode/src/view/common/Alert.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import * as React from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type ContainerProps = {
|
||||
type: 'warning' | 'error';
|
||||
inverse?: boolean;
|
||||
};
|
||||
|
||||
const getBackgroundColor = ({ type, inverse }: ContainerProps): string => {
|
||||
if (!inverse) {
|
||||
return 'var(--vscode-notifications-background)';
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'var(--vscode-editorWarning-foreground)';
|
||||
case 'error':
|
||||
return 'var(--vscode-debugExceptionWidget-border)';
|
||||
}
|
||||
};
|
||||
|
||||
const getTextColor = ({ type, inverse }: ContainerProps): string => {
|
||||
if (!inverse) {
|
||||
return 'var(--vscode-tab-activeForeground)';
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'var(--vscode-panel-background)';
|
||||
case 'error':
|
||||
return 'var(--vscode-list-activeSelectionForeground)';
|
||||
}
|
||||
};
|
||||
|
||||
const getBorderColor = ({ type, inverse }: ContainerProps): string => {
|
||||
if (inverse) {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'var(--vscode-inputValidation-warningBorder)';
|
||||
case 'error':
|
||||
return 'var(--vscode-inputValidation-errorBorder)';
|
||||
}
|
||||
} else {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'var(--vscode-notificationsWarningIcon-foreground)';
|
||||
case 'error':
|
||||
return 'var(--vscode-notificationsErrorIcon-foreground)';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Container = styled.div<ContainerProps>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1em;
|
||||
padding: 1em;
|
||||
|
||||
color: ${props => getTextColor(props)};
|
||||
background-color: ${props => getBackgroundColor(props)};
|
||||
border: 1px solid ${props => getBorderColor(props)};
|
||||
`;
|
||||
|
||||
const Title = styled.div`
|
||||
font-size: 0.85em;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
|
||||
const ActionsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.75em;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
type: 'warning' | 'error';
|
||||
title: string;
|
||||
message: ReactNode;
|
||||
|
||||
actions?: ReactNode;
|
||||
|
||||
// Inverse the color scheme
|
||||
inverse?: boolean;
|
||||
};
|
||||
|
||||
export const Alert = ({ type, title, message, actions, inverse }: Props) => {
|
||||
return (
|
||||
<Container type={type} inverse={inverse}>
|
||||
<Title>{type}: {title}</Title>
|
||||
<span>{message}</span>
|
||||
{actions && <ActionsContainer>{actions}</ActionsContainer>}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import * as React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Alert } from '../Alert';
|
||||
|
||||
describe(Alert.name, () => {
|
||||
it('renders a warning correctly', () => {
|
||||
render(<Alert type="warning" title="Warning title" message="Warning content" />);
|
||||
|
||||
expect(screen.getByText('warning: Warning title')).toBeInTheDocument();
|
||||
expect(screen.getByText('Warning content')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders an error correctly', () => {
|
||||
render(<Alert type="error" title="Error title" message="Error content" />);
|
||||
|
||||
expect(screen.getByText('error: Error title')).toBeInTheDocument();
|
||||
expect(screen.getByText('Error content')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders actions correctly', () => {
|
||||
render(
|
||||
<Alert
|
||||
type="error"
|
||||
title="Error title"
|
||||
message="Error content"
|
||||
actions={<>My actions content</>}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('My actions content')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './icon';
|
||||
export * from './Alert';
|
||||
export * from './CodePaths';
|
||||
export * from './FileCodeSnippet';
|
||||
export * from './HorizontalSpace';
|
||||
|
||||
Reference in New Issue
Block a user