xref: /expo/docs/ui/components/Callout/index.tsx (revision 79f4e8ce)
1import { css } from '@emotion/react';
2import {
3  borderRadius,
4  iconSize,
5  theme,
6  typography,
7  ErrorIcon,
8  InfoIcon,
9  WarningIcon,
10} from '@expo/styleguide';
11import { IconProps } from '@expo/styleguide/dist/types';
12import React, { ComponentType, PropsWithChildren } from 'react';
13
14type CalloutType = 'info' | 'warning' | 'error';
15
16type CalloutProps = PropsWithChildren<{
17  type?: CalloutType;
18  icon?: ComponentType<IconProps> | string;
19}>;
20
21export const Callout = ({ type = 'info', icon, children, ...rest }: CalloutProps) => {
22  const Icon = icon || getCalloutIcon(type);
23  return (
24    <div css={[containerStyle, getCalloutColor(type)]} {...rest} data-testid="callout-container">
25      <i css={iconStyle}>{typeof icon === 'string' ? icon : <Icon size={iconSize.small} />}</i>
26      <div css={contentStyle}>{children}</div>
27    </div>
28  );
29};
30
31function getCalloutColor(type: CalloutType) {
32  switch (type) {
33    case 'warning':
34      return warningColorStyle;
35    case 'error':
36      return errorColorStyle;
37    default:
38      return null;
39  }
40}
41
42function getCalloutIcon(type: CalloutType) {
43  switch (type) {
44    case 'warning':
45      return WarningIcon;
46    case 'error':
47      return ErrorIcon;
48    default:
49      return InfoIcon;
50  }
51}
52
53const containerStyle = css({
54  backgroundColor: theme.background.secondary,
55  border: `1px solid ${theme.border.default}`,
56  borderRadius: borderRadius.medium,
57  display: 'flex',
58  padding: '1rem',
59});
60
61const iconStyle = css({
62  fontStyle: 'normal',
63  marginRight: '0.5rem',
64  userSelect: 'none',
65});
66
67const contentStyle = css({
68  ...typography.body.paragraph,
69  color: theme.text.default,
70  // Markdown adds paragraphs inside blockquotes, which is useful for multiline blockquotes.
71  // We need to forcefully remove the bottom margin on the last (or only) paragraph.
72  'p:last-child': {
73    marginBottom: '0 !important', // TODO(cedric): Find an alternative for important
74  },
75});
76
77const warningColorStyle = css({
78  backgroundColor: theme.background.warning,
79  borderColor: theme.border.warning,
80});
81
82const errorColorStyle = css({
83  backgroundColor: theme.background.error,
84  borderColor: theme.border.error,
85});
86