interface LastSyncProps {
short?: boolean;
- info: Required<GithubStatusEnabled>['lastSync'];
+ info: GithubStatusEnabled['lastSync'];
}
interface GitHubSynchronisationWarningProps {
}
function LastSyncAlert({ info, short }: LastSyncProps) {
+ if (info === undefined) {
+ return null;
+ }
const { finishedAt, errorMessage, status, summary } = info;
const formattedDate = finishedAt ? formatDistance(new Date(finishedAt), new Date()) : '';
);
}
- return status === TaskStatuses.Success ? (
- <Alert variant="success">
- {translateWithParameters(
- 'settings.authentication.github.synchronization_successful',
- formattedDate
+ return (
+ <Alert
+ variant={status === TaskStatuses.Success ? 'success' : 'error'}
+ role="alert"
+ aria-live="assertive"
+ >
+ {status === TaskStatuses.Success ? (
+ <>
+ {translateWithParameters(
+ 'settings.authentication.github.synchronization_successful',
+ formattedDate
+ )}
+ <br />
+ {summary ?? ''}
+ </>
+ ) : (
+ <React.Fragment key={`synch-alert-${finishedAt}`}>
+ <div>
+ {translateWithParameters(
+ 'settings.authentication.github.synchronization_failed',
+ formattedDate
+ )}
+ </div>
+ <br />
+ {errorMessage ?? ''}
+ </React.Fragment>
)}
- <br />
- {summary ?? ''}
- </Alert>
- ) : (
- <Alert variant="error">
- <div>
- {translateWithParameters(
- 'settings.authentication.github.synchronization_failed',
- formattedDate
- )}
- </div>
- <br />
- {errorMessage ?? ''}
</Alert>
);
}
return (
<>
- {!short && data?.nextSync && (
- <>
- <Alert variant="loading" className="spacer-bottom">
- {translate(
- data.nextSync.status === TaskStatuses.Pending
- ? 'settings.authentication.github.synchronization_pending'
- : 'settings.authentication.github.synchronization_in_progress'
- )}
- </Alert>
- <br />
- </>
- )}
- {data?.lastSync && <LastSyncAlert short={short} info={data.lastSync} />}
+ <Alert
+ variant="loading"
+ className="spacer-bottom"
+ aria-atomic={true}
+ role="alert"
+ aria-live="assertive"
+ aria-label={
+ data.nextSync === undefined
+ ? translate('settings.authentication.github.synchronization_finish')
+ : ''
+ }
+ >
+ {!short &&
+ data?.nextSync &&
+ translate(
+ data.nextSync.status === TaskStatuses.Pending
+ ? 'settings.authentication.github.synchronization_pending'
+ : 'settings.authentication.github.synchronization_in_progress'
+ )}
+ </Alert>
+
+ <LastSyncAlert short={short} info={data.lastSync} />
</>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { isEmpty, keyBy, throttle } from 'lodash';
+import classNames from 'classnames';
+import { keyBy, throttle } from 'lodash';
import * as React from 'react';
import { getValues } from '../../api/settings';
import { Alert } from '../../components/ui/Alert';
import { Feature } from '../../types/features';
import { GlobalSettingKeys, SettingValue } from '../../types/settings';
+import './SystemAnnouncement.css';
import withAvailableFeatures, {
WithAvailableFeaturesProps,
} from './available-features/withAvailableFeatures';
-import './SystemAnnouncement.css';
const THROTTLE_TIME_MS = 10000;
render() {
const { displayMessage, message } = this.state;
- if (!displayMessage || isEmpty(message)) {
- return null;
- }
return (
- <div className="system-announcement-wrapper">
+ <div className={classNames({ 'system-announcement-wrapper': displayMessage && message })}>
<Alert
className="system-announcement-banner"
title={message}
display="banner"
variant="warning"
+ aria-live="assertive"
+ role="alert"
>
- {message}
+ {displayMessage && message}
</Alert>
</div>
);
import { getValues } from '../../../api/settings';
import { renderComponent } from '../../../helpers/testReactTestingUtils';
import { Feature } from '../../../types/features';
-import { AvailableFeaturesContext } from '../available-features/AvailableFeaturesContext';
import SystemAnnouncement from '../SystemAnnouncement';
+import { AvailableFeaturesContext } from '../available-features/AvailableFeaturesContext';
jest.mock('../../../api/settings', () => ({
getValues: jest.fn(),
},
} = this.props;
- if (notificationType === undefined) {
- return null;
- }
-
return (
<IndexationNotificationRenderer
type={notificationType}
*/
/* eslint-disable react/no-unused-prop-types */
+import classNames from 'classnames';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import Link from '../../../components/common/Link';
import { TaskStatuses, TaskTypes } from '../../../types/tasks';
export interface IndexationNotificationRendererProps {
- type: IndexationNotificationType;
+ type?: IndexationNotificationType;
percentCompleted: number;
isSystemAdmin: boolean;
}
const { type } = props;
return (
- <div className="indexation-notification-wrapper">
+ <div className={classNames({ 'indexation-notification-wrapper': type })}>
<Alert
className="indexation-notification-banner"
display="banner"
- variant={NOTIFICATION_VARIANTS[type]}
+ variant={type ? NOTIFICATION_VARIANTS[type] : 'success'}
+ aria-live="assertive"
>
- <div className="display-flex-center">
- {type === IndexationNotificationType.Completed && renderCompletedBanner(props)}
- {type === IndexationNotificationType.CompletedWithFailure &&
- renderCompletedWithFailureBanner(props)}
- {type === IndexationNotificationType.InProgress && renderInProgressBanner(props)}
- {type === IndexationNotificationType.InProgressWithFailure &&
- renderInProgressWithFailureBanner(props)}
- </div>
+ {type !== undefined && (
+ <div className="display-flex-center">
+ {type === IndexationNotificationType.Completed && renderCompletedBanner(props)}
+ {type === IndexationNotificationType.CompletedWithFailure &&
+ renderCompletedWithFailureBanner(props)}
+ {type === IndexationNotificationType.InProgress && renderInProgressBanner(props)}
+ {type === IndexationNotificationType.InProgressWithFailure &&
+ renderInProgressWithFailureBanner(props)}
+ </div>
+ )}
</Alert>
</div>
);
className="indexation-notification-wrapper"
>
<Alert
+ aria-live="assertive"
className="indexation-notification-banner"
display="banner"
variant="success"
className="indexation-notification-wrapper"
>
<Alert
+ aria-live="assertive"
className="indexation-notification-banner"
display="banner"
variant="success"
className="indexation-notification-wrapper"
>
<Alert
+ aria-live="assertive"
className="indexation-notification-banner"
display="banner"
variant="error"
className="indexation-notification-wrapper"
>
<Alert
+ aria-live="assertive"
className="indexation-notification-banner"
display="banner"
variant="error"
className="indexation-notification-wrapper"
>
<Alert
+ aria-live="assertive"
className="indexation-notification-banner"
display="banner"
variant="warning"
className="indexation-notification-wrapper"
>
<Alert
+ aria-live="assertive"
className="indexation-notification-banner"
display="banner"
variant="warning"
className="indexation-notification-wrapper"
>
<Alert
+ aria-live="assertive"
className="indexation-notification-banner"
display="banner"
variant="error"
className="indexation-notification-wrapper"
>
<Alert
+ aria-live="assertive"
className="indexation-notification-banner"
display="banner"
variant="error"
{translate('project.info.notifications')}
</h3>
- <Alert className="spacer-top" variant="info" aria-live="off">
+ <Alert className="spacer-top" variant="info">
{translate('notification.dispatcher.information')}
</Alert>
// Should search with empty results
almIntegrationHandler.setSearchAzureRepositories([]);
await user.keyboard('f');
- expect(screen.getByRole('alert')).toHaveTextContent('onboarding.create_project.azure.no_results');
+ expect(screen.getByText('onboarding.create_project.azure.no_results')).toBeInTheDocument();
});
function renderCreateProject(props: Partial<CreateProjectPageProps> = {}) {
await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketserver-2/]);
});
- expect(screen.getByRole('alert')).toHaveTextContent('onboarding.create_project.no_bbs_projects');
+ expect(screen.getByText('onboarding.create_project.no_bbs_projects')).toBeInTheDocument();
});
function renderCreateProject(props: Partial<CreateProjectPageProps> = {}) {
await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-2/]);
});
- expect(screen.getByRole('alert')).toHaveTextContent(
- 'onboarding.create_project.bitbucketcloud.no_projects'
- );
+ expect(
+ screen.getByText('onboarding.create_project.bitbucketcloud.no_projects')
+ ).toBeInTheDocument();
});
it('should have load more', async () => {
await selectEvent.select(ui.instanceSelector.get(), [/conf-final-2/]);
});
- expect(screen.getByRole('alert')).toHaveTextContent(
- 'onboarding.create_project.gitlab.no_projects'
- );
+ expect(screen.getByText('onboarding.create_project.gitlab.no_projects')).toBeInTheDocument();
});
it('should display a warning if the instance default new code definition is not CaYC compliant', async () => {
expect(screen.getByText('Gitlab project 1')).toBeInTheDocument();
expect(screen.getByText('Gitlab project 2')).toBeInTheDocument();
- expect(screen.getByRole('alert')).toHaveTextContent(
- 'onboarding.create_project.new_code_option.warning.title'
- );
+ expect(
+ screen.getByText('onboarding.create_project.new_code_option.warning.title')
+ ).toBeInTheDocument();
});
function renderCreateProject(props: Partial<CreateProjectPageProps> = {}) {
it('should render Empty Overview for Application with no analysis', async () => {
renderApp({ component: mockComponent({ qualifier: ComponentQualifier.Application }) });
- expect(
- await screen.findByRole('alert', { name: 'provisioning.no_analysis.application' })
- ).toBeInTheDocument();
+ expect(await screen.findByText('provisioning.no_analysis.application')).toBeInTheDocument();
});
it('should render Empty Overview on main branch with no analysis', async () => {
renderApp({}, mockCurrentUser());
expect(
- await screen.findByRole('alert', { name: 'provisioning.no_analysis_on_main_branch.master' })
+ await screen.findByText('provisioning.no_analysis_on_main_branch.master')
).toBeInTheDocument();
});
renderApp({ branchLikes: [mockBranch(), mockBranch()] });
expect(
- await screen.findByRole('alert', {
- name: 'provisioning.no_analysis_on_main_branch.bad_configuration.master.branches.main_branch',
- })
+ await screen.findByText(
+ 'provisioning.no_analysis_on_main_branch.bad_configuration.master.branches.main_branch'
+ )
).toBeInTheDocument();
});
import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization';
import { mockComponent } from '../../../helpers/mocks/component';
import {
- renderAppWithComponentContext,
RenderContext,
+ renderAppWithComponentContext,
} from '../../../helpers/testReactTestingUtils';
import { Component } from '../../../types/types';
import routes from '../routes';
saveButton: byRole('button', { name: 'save' }),
statusMessage: byRole('status'),
noConditionsNewCodeWarning: byText('project_quality_gate.no_condition_on_new_code'),
- alertMessage: byRole('alert'),
+ alertMessage: byText('unknown'),
};
beforeAll(() => {
handler.setThrowOnGetGateForProject(true);
renderProjectQualityGateApp();
- expect(await ui.alertMessage.find()).toHaveTextContent('unknown');
+ expect(await ui.alertMessage.find()).toBeInTheDocument();
expect(ui.qualityGateHeading.query()).not.toBeInTheDocument();
});
const heading = await screen.findByRole('heading', { name: 'login.login_to_sonarqube' });
expect(heading).toBeInTheDocument();
- expect(screen.getByRole('alert')).toBeInTheDocument();
expect(screen.getByText('login.unauthorized_access_alert')).toBeInTheDocument();
});
expect(ui.daysInput.get()).toHaveValue('91');
// Should warn about non compliant value
- expect(screen.getByRole('alert')).toHaveTextContent(
- 'baseline.number_days.compliance_warning.title'
- );
+ expect(screen.getByText('baseline.number_days.compliance_warning.title')).toBeInTheDocument();
await user.clear(ui.daysInput.get());
await user.type(ui.daysInput.get(), '92');
*/
import userEvent from '@testing-library/user-event';
import React from 'react';
-import { byRole } from 'testing-library-selector';
+import { byRole, byText } from 'testing-library-selector';
import AlmSettingsServiceMock from '../../../../../api/mocks/AlmSettingsServiceMock';
import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
import { AlmKeys } from '../../../../../types/alm-settings';
byRole('textbox', { name: `settings.almintegration.form.${id}` }),
saveConfigurationButton: byRole('button', { name: 'settings.almintegration.form.save' }),
cancelButton: byRole('button', { name: 'cancel' }),
- validationError: byRole('alert'),
+ validationError: (text: string) => byText(text),
};
const onCancel = jest.fn();
await userEvent.type(ui.configurationInput('personal_access_token').get(), 'Access Token');
await userEvent.click(ui.saveConfigurationButton.get());
- expect(ui.validationError.get()).toHaveTextContent('Validation Error');
+ expect(ui.validationError('Validation Error').get()).toBeInTheDocument();
await userEvent.click(ui.cancelButton.get());
expect(onCancel).toHaveBeenCalled();
confirmDelete: byRole('button', { name: 'delete' }),
checkConfigurationButton: (key: string) =>
byRole('button', { name: `settings.almintegration.check_configuration_x.${key}` }),
- validationErrorMessage: byRole('alert'),
- validationSuccessMessage: byRole('status'),
+ validationMessage: (text: string) => byText(text),
};
async function createConfiguration(
// Existing configuration is edited
expect(screen.queryByRole('heading', { name: currentName })).not.toBeInTheDocument();
expect(screen.getByRole('heading', { name: newName })).toBeInTheDocument();
- expect(ui.validationErrorMessage.get()).toHaveTextContent('Something is wrong');
+ expect(ui.validationMessage('Something is wrong').get()).toBeInTheDocument();
}
async function checkConfiguration(name: string) {
almSettings.setDefinitionErrorMessage('');
await userEvent.click(ui.checkConfigurationButton(name).get());
- expect(ui.validationSuccessMessage.getAll()[0]).toHaveTextContent(
- 'alert.tooltip.successsettings.almintegration.configuration_valid'
- );
+ expect(
+ ui.validationMessage('settings.almintegration.configuration_valid').getAll()[0]
+ ).toBeInTheDocument();
}
async function deleteConfiguration(name: string) {
return (
<>
- <Alert title={messages[0]} variant={alertVariant}>
+ <Alert
+ title={messages[0]}
+ variant={alertVariant}
+ aria-live="polite"
+ role="status"
+ aria-atomic={true}
+ aria-busy={isFetching}
+ >
<div className="sw-flex sw-justify-between sw-items-center">
<div>
{messages.map((msg) => (
configurationValiditySuccess: byRole('status', {
name: /github.configuration.validation.valid/,
}),
- configurationValidityError: byRole('alert', {
+ configurationValidityError: byRole('status', {
name: /github.configuration.validation.invalid/,
}),
checkConfigButton: byRole('button', {
import userEvent from '@testing-library/user-event';
import React from 'react';
import selectEvent from 'react-select-event';
-import { byRole } from 'testing-library-selector';
+import { byRole, byText } from 'testing-library-selector';
import AlmSettingsServiceMock from '../../../../../api/mocks/AlmSettingsServiceMock';
import CurrentUserContextProvider from '../../../../../app/components/current-user/CurrentUserContextProvider';
import { mockComponent } from '../../../../../helpers/mocks/component';
}
// Save form and check for errors
await user.click(ui.saveButton.get());
- expect(ui.validationErrorMsg.get()).toHaveTextContent('cute error');
+ expect(ui.validationMsg('cute error').get()).toBeInTheDocument();
// Check validation with errors
await user.click(ui.validateButton.get());
- expect(ui.validationErrorMsg.get()).toHaveTextContent('cute error');
+ expect(ui.validationMsg('cute error').get()).toBeInTheDocument();
// Save form and check for errors
almSettings.setProjectBindingConfigurationErrors(undefined);
'Anything'
);
await user.click(ui.saveButton.get());
- expect(await ui.validationSuccessMsg.find()).toHaveTextContent(
- 'settings.pr_decoration.binding.check_configuration.success'
- );
+ expect(
+ await ui.validationMsg('settings.pr_decoration.binding.check_configuration.success').find()
+ ).toBeInTheDocument();
await user.click(ui.validateButton.get());
- expect(ui.validationSuccessMsg.get()).toHaveTextContent(
- 'settings.pr_decoration.binding.check_configuration.success'
- );
+ expect(
+ ui.validationMsg('settings.pr_decoration.binding.check_configuration.success').get()
+ ).toBeInTheDocument();
// Rerender and verify that validation is done for binding
rerender(
<MockedPRDecorationBinding component={mockComponent()} currentUser={mockCurrentUser()} />
);
- expect(await ui.validationSuccessMsg.find()).toHaveTextContent(
- 'settings.pr_decoration.binding.check_configuration.success'
- );
+ expect(
+ await ui.validationMsg('settings.pr_decoration.binding.check_configuration.success').find()
+ ).toBeInTheDocument();
expect(ui.saveButton.query()).not.toBeInTheDocument();
// Reset binding
validateButton: byRole('button', {
name: 'settings.pr_decoration.binding.check_configuration',
}),
- validationErrorMsg: byRole('alert'),
- validationSuccessMsg: byRole('status'),
+ validationMsg: (text: string) => byText(text),
setInput,
};
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { first } from 'lodash';
-import { byRole } from 'testing-library-selector';
+import { byRole, byText } from 'testing-library-selector';
import SystemServiceMock from '../../../../api/mocks/SystemServiceMock';
import { renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
import routes from '../../routes';
await user.click(ui.changeLogLevelButton.get());
expect(ui.logLevelWarning.queryAll()).toHaveLength(0);
await user.click(ui.logLevelsRadioButton(LogsLevels.DEBUG).get());
- expect(ui.logLevelWarning.get()).toHaveTextContent(
- 'alert.tooltip.warningsystem.log_level.warning'
- );
+ expect(ui.logLevelWarning.get()).toBeInTheDocument();
await user.click(ui.saveButton.get());
- expect(ui.logLevelWarning.queryAll()).toHaveLength(2);
+ expect(ui.logLevelWarningShort.queryAll()).toHaveLength(2);
});
it('can download logs & system info', async () => {
);
// Renders health checks
- expect(ui.healthCauseWarning.getAll()).toHaveLength(3);
+ expect(ui.healthCauseWarning.get()).toBeInTheDocument();
// Renders App node
expect(first(ui.sectionButton('server1.example.com').getAll())).toBeInTheDocument();
sectionButton: (name: string) => byRole('button', { name }),
changeLogLevelButton: byRole('button', { name: 'system.logs_level.change' }),
logLevelsRadioButton: (name: LogsLevels) => byRole('radio', { name }),
- logLevelWarning: byRole('alert'),
- healthCauseWarning: byRole('alert'),
+ logLevelWarning: byText('system.log_level.warning'),
+ logLevelWarningShort: byText('system.log_level.warning.short'),
+ healthCauseWarning: byText('Friendly warning'),
saveButton: byRole('button', { name: 'save' }),
};
import UserTokensMock from '../../../../api/mocks/UserTokensMock';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockLanguage, mockLoggedInUser } from '../../../../helpers/testMocks';
-import { renderApp, RenderContext } from '../../../../helpers/testReactTestingUtils';
+import { RenderContext, renderApp } from '../../../../helpers/testReactTestingUtils';
import { Permissions } from '../../../../types/permissions';
import { TokenType } from '../../../../types/token';
import { getCopyToClipboardValue } from '../../test-utils';
const modal = screen.getByRole('dialog');
await clickButton(user, 'onboarding.token.generate', modal);
const lastToken = tokenMock.getLastToken();
- if (lastToken === undefined) {
- throw new Error("Couldn't find the latest generated token.");
- }
- expect(lastToken.type).toBe(TokenType.Global);
- expect(within(modal).getByRole('alert')).toHaveTextContent(
- `users.tokens.new_token_created.${lastToken.token}`
- );
+
+ expect(lastToken).toBeDefined();
+
+ expect(lastToken!.type).toBe(TokenType.Global);
+ expect(
+ within(modal).getByText(`users.tokens.new_token_created.${lastToken!.token}`)
+ ).toBeInTheDocument();
await clickButton(user, 'continue', modal);
// Continue.
expect(lastToken.type).toBe(TokenType.Project);
expect(lastToken.expirationDate).toBe(computeTokenExpirationDate(365));
- expect(screen.getByRole('alert')).toHaveTextContent(
- `users.tokens.new_token_created.${lastToken.token}`
- );
+ expect(screen.getByText(`users.tokens.new_token_created.${lastToken.token}`)).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'copy_to_clipboard' })).toBeInTheDocument();
// Revoke token.
}
expect(lastToken.type).toBe(TokenType.Project);
expect(lastToken.expirationDate).toBe(computeTokenExpirationDate(365));
- expect(screen.getByRole('alert')).toHaveTextContent(
- `users.tokens.new_token_created.${lastToken.token}`
- );
+ expect(screen.getByText(`users.tokens.new_token_created.${lastToken.token}`)).toBeInTheDocument();
});
it('should allow setting a preferred token type', async () => {
import * as React from 'react';
import { colors, sizes } from '../../app/theme';
import { translate } from '../../helpers/l10n';
-import { Dict } from '../../types/types';
import AlertErrorIcon from '../icons/AlertErrorIcon';
import AlertSuccessIcon from '../icons/AlertSuccessIcon';
import AlertWarnIcon from '../icons/AlertWarnIcon';
export interface AlertProps {
display?: AlertDisplay;
variant: AlertVariant;
-}
-
-interface AlertVariantInformation {
- icon: JSX.Element;
- color: string;
- borderColor: string;
- backGroundColor: string;
- role: string;
+ live?: boolean;
}
const DOUBLE = 2;
const QUADRUPLE = 4;
-const StyledAlertIcon = styled.div<{ isBanner: boolean; variantInfo: AlertVariantInformation }>`
- flex: 0 0 auto;
- display: flex;
- justify-content: center;
- align-items: center;
- width: calc(${({ isBanner }) => (isBanner ? DOUBLE : QUADRUPLE)} * ${sizes.gridSize});
- border-right: ${({ isBanner }) => (!isBanner ? '1px solid' : 'none')};
- border-color: ${({ variantInfo }) => variantInfo.borderColor};
-`;
-
-const StyledAlertContent = styled.div`
- flex: 1 1 auto;
- overflow: auto;
- text-align: left;
- padding: ${sizes.gridSize} calc(2 * ${sizes.gridSize});
-`;
-
const alertInnerIsBannerMixin = () => css`
min-width: ${sizes.minPageWidth};
max-width: ${sizes.maxPageWidth};
box-sizing: border-box;
`;
-const StyledAlertInner = styled.div<{ isBanner: boolean }>`
- display: flex;
- align-items: stretch;
- ${({ isBanner }) => (isBanner ? alertInnerIsBannerMixin : null)}
-`;
-
-const StyledAlert = styled.div<{ isInline: boolean; variantInfo: AlertVariantInformation }>`
+const StyledAlert = styled.div<{
+ isInline: boolean;
+ color: string;
+ backGroundColor: string;
+ borderColor: string;
+ isBanner: boolean;
+}>`
border: 1px solid;
border-radius: 2px;
margin-bottom: ${sizes.gridSize};
- border-color: ${({ variantInfo }) => variantInfo.borderColor};
- background-color: ${({ variantInfo }) => variantInfo.backGroundColor};
- color: ${({ variantInfo }) => variantInfo.color};
+ border-color: ${({ borderColor }) => borderColor};
+ background-color: ${({ backGroundColor }) => backGroundColor};
+ color: ${({ color }) => color};
display: ${({ isInline }) => (isInline ? 'inline-block' : 'block')};
:empty {
.button-link:hover {
border-color: ${colors.darkBlue};
}
+
+ & .alert-inner {
+ display: flex;
+ align-items: stretch;
+ ${({ isBanner }) => (isBanner ? alertInnerIsBannerMixin : null)}
+ }
+
+ & .alert-icon {
+ flex: 0 0 auto;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: calc(${({ isBanner }) => (isBanner ? DOUBLE : QUADRUPLE)} * ${sizes.gridSize});
+ border-right: ${({ isBanner }) => (!isBanner ? '1px solid' : 'none')};
+ border-color: ${({ borderColor }) => borderColor};
+ }
+
+ & .alert-content {
+ flex: 1 1 auto;
+ overflow: auto;
+ text-align: left;
+ padding: ${sizes.gridSize} calc(2 * ${sizes.gridSize});
+ }
`;
-function getAlertVariantInfo(variant: AlertVariant): AlertVariantInformation {
- const variantList: Dict<AlertVariantInformation> = {
+function getAlertVariantInfo(variant: AlertVariant) {
+ const variantList = {
error: {
icon: (
<AlertErrorIcon label={translate('alert.tooltip.error')} fill={colors.alertIconError} />
color: colors.alertTextError,
borderColor: colors.alertBorderError,
backGroundColor: colors.alertBackgroundError,
- role: 'alert',
},
warning: {
icon: (
color: colors.alertTextWarning,
borderColor: colors.alertBorderWarning,
backGroundColor: colors.alertBackgroundWarning,
- role: 'alert',
},
success: {
icon: (
color: colors.alertTextSuccess,
borderColor: colors.alertBorderSuccess,
backGroundColor: colors.alertBackgroundSuccess,
- role: 'status',
},
info: {
icon: <InfoIcon label={translate('alert.tooltip.info')} fill={colors.alertIconInfo} />,
color: colors.alertTextInfo,
borderColor: colors.alertBorderInfo,
backGroundColor: colors.alertBackgroundInfo,
- role: 'status',
},
loading: {
icon: <DeferredSpinner timeout={0} />,
color: colors.alertTextInfo,
borderColor: colors.alertBorderInfo,
backGroundColor: colors.alertBackgroundInfo,
- role: 'status',
},
- };
+ } as const;
return variantList[variant];
}
export function Alert(props: AlertProps & React.HTMLAttributes<HTMLDivElement>) {
- const { className, display, variant, ...domProps } = props;
+ const { className, display, variant, children, live, ...domProps } = props;
const isInline = display === 'inline';
const isBanner = display === 'banner';
const variantInfo = getAlertVariantInfo(variant);
return (
<StyledAlert
className={classNames('alert', className)}
+ isBanner={isBanner}
isInline={isInline}
- role={variantInfo.role}
- variantInfo={variantInfo}
+ color={variantInfo.color}
+ borderColor={variantInfo.borderColor}
+ backGroundColor={variantInfo.backGroundColor}
{...domProps}
>
- <StyledAlertInner isBanner={isBanner}>
- <StyledAlertIcon isBanner={isBanner} variantInfo={variantInfo}>
- {variantInfo.icon}
- </StyledAlertIcon>
- <StyledAlertContent className="alert-content">{props.children}</StyledAlertContent>
- </StyledAlertInner>
+ {children && (
+ <div className="alert-inner">
+ <div className="alert-icon">{variantInfo.icon}</div>
+ <div className="alert-content">{children}</div>
+ </div>
+ )}
</StyledAlert>
);
}
border-color: #236a97;
}
-.emotion-1 {
+.emotion-0 .alert-inner {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
box-sizing: border-box;
}
-.emotion-2 {
+.emotion-0 .alert-icon {
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
border-color: #f4b1b0;
}
-.emotion-3 {
+.emotion-0 .alert-content {
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
<div
class="alert alert-test emotion-0"
+ color="#862422"
id="error-message"
- role="alert"
>
<div
- class="emotion-1"
+ class="alert-inner"
>
<div
- class="emotion-2"
+ class="alert-icon"
>
<svg
height="16"
</svg>
</div>
<div
- class="alert-content emotion-3"
+ class="alert-content"
>
This is an error!
</div>
exports[`should render properly 1`] = `
<Styled(div)
+ backGroundColor="#f2dede"
+ borderColor="#f4b1b0"
className="alert alert-test"
+ color="#862422"
id="error-message"
+ isBanner={false}
isInline={false}
- role="alert"
- variantInfo={
- {
- "backGroundColor": "#f2dede",
- "borderColor": "#f4b1b0",
- "color": "#862422",
- "icon": <AlertErrorIcon
- fill="#a4030f"
- label="alert.tooltip.error"
- />,
- "role": "alert",
- }
- }
>
- <Styled(div)
- isBanner={false}
+ <div
+ className="alert-inner"
>
- <Styled(div)
- isBanner={false}
- variantInfo={
- {
- "backGroundColor": "#f2dede",
- "borderColor": "#f4b1b0",
- "color": "#862422",
- "icon": <AlertErrorIcon
- fill="#a4030f"
- label="alert.tooltip.error"
- />,
- "role": "alert",
- }
- }
+ <div
+ className="alert-icon"
>
<AlertErrorIcon
fill="#a4030f"
label="alert.tooltip.error"
/>
- </Styled(div)>
- <Styled(div)
+ </div>
+ <div
className="alert-content"
>
This is an error!
- </Styled(div)>
- </Styled(div)>
+ </div>
+ </div>
</Styled(div)>
`;
-exports[`verification of all variants of alert 1`] = `
-{
- "backGroundColor": "#f2dede",
- "borderColor": "#f4b1b0",
- "color": "#862422",
- "icon": <AlertErrorIcon
- fill="#a4030f"
- label="alert.tooltip.error"
- />,
- "role": "alert",
-}
-`;
+exports[`verification of all variants of alert 1`] = `undefined`;
-exports[`verification of all variants of alert 2`] = `
-{
- "backGroundColor": "#fcf8e3",
- "borderColor": "#faebcc",
- "color": "#6f4f17",
- "icon": <AlertWarnIcon
- fill="#db781a"
- label="alert.tooltip.warning"
- />,
- "role": "alert",
-}
-`;
+exports[`verification of all variants of alert 2`] = `undefined`;
-exports[`verification of all variants of alert 3`] = `
-{
- "backGroundColor": "#dff0d8",
- "borderColor": "#d6e9c6",
- "color": "#215821",
- "icon": <AlertSuccessIcon
- fill="#6d9867"
- label="alert.tooltip.success"
- />,
- "role": "status",
-}
-`;
+exports[`verification of all variants of alert 3`] = `undefined`;
-exports[`verification of all variants of alert 4`] = `
-{
- "backGroundColor": "#d9edf7",
- "borderColor": "#b1dff3",
- "color": "#0e516f",
- "icon": <InfoIcon
- fill="#0271b9"
- label="alert.tooltip.info"
- />,
- "role": "status",
-}
-`;
+exports[`verification of all variants of alert 4`] = `undefined`;
-exports[`verification of all variants of alert 5`] = `
-{
- "backGroundColor": "#d9edf7",
- "borderColor": "#b1dff3",
- "color": "#0e516f",
- "icon": <DeferredSpinner
- timeout={0}
- />,
- "role": "status",
-}
-`;
+exports[`verification of all variants of alert 5`] = `undefined`;
settings.authentication.github.synchronize_now=Synchronize now
settings.authentication.github.synchronization_in_progress=Synchronization is in progress.
settings.authentication.github.synchronization_pending=Synchronization is scheduled.
+settings.authentication.github.synchronization_finish=Synchronization is done.
settings.authentication.github.synchronization_successful=Last synchronization was done {0} ago.
settings.authentication.github.synchronization_failed=Last synchronization failed {0} ago.
settings.authentication.github.synchronization_failed_short=Last synchronization failed. {details}