Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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 HeaderStandard 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,19 @@
import React from 'react';

import { IconSize } from '../../types';
import { HeaderStandard } from '../HeaderStandard';
import { IconAlert } from '../IconAlert';

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

export const HeaderAlert: React.FC<HeaderAlertProps> = ({
severity,
iconAlertProps,
...headerStandardProps
}) => (
<HeaderStandard {...headerStandardProps}>
<IconAlert {...iconAlertProps} severity={severity} size={IconSize.Lg} />
</HeaderStandard>
);

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

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

/**
* HeaderAlert component props (React Native).
*/
export type HeaderAlertProps = Omit<
HeaderStandardProps,
'children' | 'title' | 'titleProps' | 'subtitle' | 'subtitleProps'
> &
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'>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# HeaderAlert

HeaderAlert is a [`HeaderStandard`](./../HeaderStandard/README.md) layout whose center content is always an [`IconAlert`](./../IconAlert/README.md) at `IconSize.Lg`, driven by a required `severity` that matches [`IconAlert`](./../IconAlert/README.md) semantics.

The center slot is always the `IconAlert`; `title`, `titleProps`, `subtitle`, `subtitleProps`, and `children` are not part of the public API. Pass actions such as `onBack` or `onClose` as you would on `HeaderStandard`.

```tsx
import {
HeaderAlert,
IconAlertSeverity,
} from '@metamask/design-system-react-native';

<HeaderAlert severity={IconAlertSeverity.Warning} onClose={() => {}} />;
```

## Props

### `severity`

Same values and icon/color mapping as [`IconAlert` `severity`](./../IconAlert/README.md). This value is always passed to the inner `IconAlert` after `iconAlertProps`, so it overrides any `severity` inside `iconAlertProps`.

| TYPE | REQUIRED | DEFAULT |
| ------------------- | -------- | ------- |
| `IconAlertSeverity` | Yes | - |

```tsx
import {
HeaderAlert,
IconAlertSeverity,
} from '@metamask/design-system-react-native';

<HeaderAlert severity={IconAlertSeverity.Error} onClose={handleClose} />;
```

### `iconAlertProps`

Optional props spread onto the inner `IconAlert` (for example `testID`, `twClassName`, `accessible`). The type is `IconAlert` props with `severity` and `size` omitted, because `HeaderAlert` always supplies those (`severity` from this component; `size` is `IconSize.Lg`).

| TYPE | REQUIRED | DEFAULT |
| -------------------------------------------- | -------- | ----------- |
| `Omit<IconAlertProps, 'severity' \| 'size'>` | No | `undefined` |

```tsx
import {
HeaderAlert,
IconAlertSeverity,
} from '@metamask/design-system-react-native';

<HeaderAlert
severity={IconAlertSeverity.Info}
onClose={handleClose}
iconAlertProps={{ testID: 'alert-header-icon', accessible: true }}
/>;
```

### Other props

All [`HeaderStandard`](./../HeaderStandard/README.md) props except `children`, `title`, `titleProps`, `subtitle`, and `subtitleProps` are supported (`onBack`, `onClose`, `twClassName`, `testID`, etc.).

## References

- Storybook: **Components / HeaderAlert** — `Default`, `Severity`
- [MetaMask Design System Guides](https://www.notion.so/MetaMask-Design-System-Guides-Design-f86ecc914d6b4eb6873a122b83c12940)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { IconAlertSeverity } from '@metamask/design-system-shared';
export { HeaderAlert } from './HeaderAlert';
export type { HeaderAlertProps } from './HeaderAlert.types';
3 changes: 3 additions & 0 deletions packages/design-system-react-native/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ export type { CheckboxProps } from './Checkbox';
export { HeaderBase, HeaderBaseVariant } from './HeaderBase';
export type { HeaderBaseProps } from './HeaderBase';

export { HeaderAlert } from './HeaderAlert';
export type { HeaderAlertProps } from './HeaderAlert';

export { HeaderRoot } from './HeaderRoot';
export type { HeaderRootProps } from './HeaderRoot';

Expand Down
3 changes: 3 additions & 0 deletions packages/design-system-shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export {
type IconAlertPropsShared,
} from './types/IconAlert';

// HeaderAlert types (ADR-0004)
export { type HeaderAlertPropsShared } from './types/HeaderAlert';

// BannerBase types (ADR-0004)
export { type BannerBasePropsShared } from './types/BannerBase';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { IconAlertPropsShared } from '../IconAlert';

/**
* HeaderAlert shared props (ADR-0004).
* Reuses IconAlert severity semantics.
*/
export type HeaderAlertPropsShared = IconAlertPropsShared;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { HeaderAlertPropsShared } from './HeaderAlert.types';
Loading