Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { IconAlertSeverity } from '@metamask/design-system-shared';
import type { Meta, StoryObj } from '@storybook/react-native';
import React from 'react';

import { Box, BoxAlignItems } from '../Box';

import { HeaderAlert } from './HeaderAlert';
import type { HeaderAlertProps } from './HeaderAlert.types';

const meta: Meta<HeaderAlertProps> = {
title: 'Components/HeaderAlert',
component: HeaderAlert,
argTypes: {
severity: {
control: 'select',
options: Object.values(IconAlertSeverity),
},
twClassName: { control: 'text' },
},
};

export default meta;

type Story = StoryObj<HeaderAlertProps>;

export const Default: Story = {
args: {
severity: IconAlertSeverity.Info,
onBack: () => null,
onClose: () => null,
},
};

export const Severity: Story = {
render: () => (
<Box gap={4}>
{Object.values(IconAlertSeverity).map((severity) => (
<Box key={severity} alignItems={BoxAlignItems.Center} gap={2}>
<HeaderAlert
severity={severity}
onBack={() => null}
onClose={() => null}
iconAlertProps={{ testID: `header-alert-icon-${severity}` }}
/>
</Box>
))}
</Box>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { IconAlertSeverity } from '@metamask/design-system-shared';
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import { renderHook } from '@testing-library/react-hooks';
import { render } from '@testing-library/react-native';
import React from 'react';

import { IconSize } from '../../types';
import { TWCLASSMAP_ICON_SIZE_DIMENSION } from '../Icon/Icon.constants';
import type { IconAlertProps } from '../IconAlert';
import { ICON_ALERT_SEVERITY_MAP } from '../IconAlert/IconAlert.constants';

import { HeaderAlert } from './HeaderAlert';

type IconAlertSeverityUnion =
(typeof IconAlertSeverity)[keyof typeof IconAlertSeverity];

describe('HeaderAlert', () => {
let tw: ReturnType<typeof useTailwind>;

beforeAll(() => {
tw = renderHook(() => useTailwind()).result.current;
});

describe('when a severity is provided', () => {
it.each(Object.values(IconAlertSeverity) as IconAlertSeverityUnion[])(
'renders IconAlert at Lg with mapped color for %s',
(severity) => {
const { getByTestId } = render(
<HeaderAlert
severity={severity}
iconAlertProps={{ testID: 'header-alert-icon' }}
/>,
);

const icon = getByTestId('header-alert-icon');
const { color } = ICON_ALERT_SEVERITY_MAP[severity];

expect(icon).toBeOnTheScreen();
expect(icon).toHaveStyle(
tw.style(color, TWCLASSMAP_ICON_SIZE_DIMENSION[IconSize.Lg]),
);
},
);
});

describe('when iconAlertProps includes a severity at runtime', () => {
it('uses HeaderAlert severity for icon mapping', () => {
const { getByTestId } = render(
<HeaderAlert
severity={IconAlertSeverity.Error}
iconAlertProps={
{
severity: IconAlertSeverity.Info,
testID: 'header-alert-icon',
} as IconAlertProps
}
/>,
);

const icon = getByTestId('header-alert-icon');
const { color } = ICON_ALERT_SEVERITY_MAP[IconAlertSeverity.Error];

expect(icon).toHaveStyle(
tw.style(color, TWCLASSMAP_ICON_SIZE_DIMENSION[IconSize.Lg]),
);
});
});

it('forwards HeaderBase props such as testID', () => {
const { getByTestId } = render(
<HeaderAlert
severity={IconAlertSeverity.Info}
testID="header-alert-root"
iconAlertProps={{ testID: 'header-alert-icon' }}
/>,
);

expect(getByTestId('header-alert-root')).toBeOnTheScreen();
expect(getByTestId('header-alert-icon')).toBeOnTheScreen();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Third party dependencies.
import React, { useMemo } from 'react';

// External dependencies.
import { IconSize } from '../../types';
import type { ButtonIconProps } from '../ButtonIcon';
import { HeaderBase } from '../HeaderBase';
import { IconName } from '../Icon';
import { IconAlert } from '../IconAlert';

// Internal dependencies.
import type { HeaderAlertProps } from './HeaderAlert.types';

export const HeaderAlert: React.FC<HeaderAlertProps> = ({
severity,
iconAlertProps,
onBack,
backButtonProps,
onClose,
closeButtonProps,
endButtonIconProps,
startButtonIconProps,
twClassName = '',
testID,
...headerBaseProps
}) => {
const resolvedStartButtonIconProps = useMemo(() => {
if (startButtonIconProps) {
return startButtonIconProps;
}
if (onBack || backButtonProps) {
const startProps: ButtonIconProps = {
iconName: IconName.ArrowLeft,
...(backButtonProps ?? {}),
onPress: backButtonProps?.onPress ?? onBack,
};
return startProps;
}
return undefined;
}, [onBack, backButtonProps, startButtonIconProps]);

const resolvedEndButtonIconProps = useMemo(() => {
const props: ButtonIconProps[] = [];

if (onClose || closeButtonProps) {
const closeProps: ButtonIconProps = {
iconName: IconName.Close,
...(closeButtonProps || {}),
onPress: closeButtonProps?.onPress ?? onClose,
};
props.push(closeProps);
}

if (endButtonIconProps) {
props.push(...endButtonIconProps);
}
return props.length > 0 ? props : undefined;
}, [endButtonIconProps, onClose, closeButtonProps]);
Comment thread
brianacnguyen marked this conversation as resolved.
Outdated

return (
<HeaderBase
testID={testID}
startButtonIconProps={resolvedStartButtonIconProps}
endButtonIconProps={resolvedEndButtonIconProps}
{...headerBaseProps}
twClassName={`px-2 ${twClassName}`}
>
<IconAlert {...iconAlertProps} severity={severity} size={IconSize.Lg} />
</HeaderBase>
);
};

HeaderAlert.displayName = 'HeaderAlert';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { HeaderAlertPropsShared } from '@metamask/design-system-shared';

import type { HeaderBaseProps } from '../HeaderBase';
import type { HeaderStandardProps } from '../HeaderStandard';
import type { IconAlertProps } from '../IconAlert';

type HeaderStandardNavigationShortcuts = Pick<
HeaderStandardProps,
'onBack' | 'backButtonProps' | 'onClose' | 'closeButtonProps'
>;

/**
* HeaderAlert component props (React Native).
*/
export type HeaderAlertProps = Omit<HeaderBaseProps, 'children'> &
HeaderStandardNavigationShortcuts &
HeaderAlertPropsShared & {
/**
* Props for the inner IconAlert. `severity` and `size` are always set by
* HeaderAlert and are omitted from this type.
*/
iconAlertProps?: Omit<IconAlertProps, 'severity' | 'size'>;
};
Loading
Loading