@apify/ui-library 1.83.1 → 1.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apify/ui-library",
3
- "version": "1.83.1",
3
+ "version": "1.84.0",
4
4
  "description": "React UI library used by apify.com",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -69,5 +69,5 @@
69
69
  "src",
70
70
  "style"
71
71
  ],
72
- "gitHead": "6606ceabaff32097eb6ae5dfabc812f5d8a0b747"
72
+ "gitHead": "9fefb950fa0e31459680e2c648acb40b98efe8f2"
73
73
  }
@@ -1,48 +1,87 @@
1
1
  import clsx from 'clsx';
2
+ import type { ReactNode } from 'react';
2
3
  import React from 'react';
3
- import styled from 'styled-components';
4
+ import styled, { css } from 'styled-components';
4
5
 
5
- import { CheckCircleIcon, CrossIcon, InfoIcon, WarningTriangleIcon } from '@apify/ui-icons';
6
+ import type { IconComponent as IconComponentType } from '@apify/ui-icons';
7
+ import { CheckIcon, CrossIcon, InfoIcon, WarningTriangleIcon, XCircleIcon } from '@apify/ui-icons';
6
8
 
7
9
  import { theme } from '../design_system/theme.js';
8
10
  import { Box, type BoxProps } from './box.js';
11
+ import type { ButtonProps } from './button.js';
9
12
  import { Button } from './button.js';
10
- import { Heading } from './text/index.js';
13
+ import { Text } from './text/index.js';
11
14
 
12
15
  export const messageClassNames = {
13
16
  main: 'Message',
14
17
  icon: 'icon',
15
18
  content: 'content',
16
19
  caption: 'caption',
20
+ description: 'description',
17
21
  dismiss: 'dismiss',
22
+ actionsWrapper: 'actionsWrapper',
18
23
  };
19
24
 
20
- const StyledMessage = styled(Box)`
25
+ const StyledMessage = styled(Box)<{ $boxless?: boolean, $hasCaption?: boolean }>`
21
26
  display: flex;
27
+ align-items: center;
22
28
  gap: ${theme.space.space8};
23
29
 
24
30
  border-radius: ${theme.radius.radius8};
25
31
  box-shadow: ${theme.shadow.shadow1};
26
32
 
27
- .${messageClassNames.content} {
28
- flex: 1;
29
- margin-top: 1px; /* This is to align icon with text (align-items: center not possible - icon should be on top) */
33
+ .${messageClassNames.icon} {
34
+ grid-area: iconArea;
35
+ color: ${theme.color.neutral.icon};
36
+ /* Icon has 16px but text 20. With this extra margin we make the elements aligned */
37
+ margin: ${theme.space.space2} 0;
30
38
  }
31
39
 
32
40
  .${messageClassNames.caption} {
33
- /* TODO: We should not override the default Heading that is rendered for caption but it doesn't look good without these */
34
- line-height: 20px;
35
- font-weight: 600;
36
- margin-bottom: ${theme.space.space4};
41
+ grid-area: captionArea;
37
42
  }
38
43
 
39
- .${messageClassNames.dismiss} {
40
- background: transparent;
41
- height: 20px;
44
+ .${messageClassNames.description} {
45
+ grid-area: descriptionArea;
46
+ }
42
47
 
43
- svg {
44
- color: ${theme.color.neutral.icon}
48
+ .${messageClassNames.content} {
49
+ flex: 1;
50
+ display: grid;
51
+ gap: ${theme.space.space6} ${theme.space.space8};
52
+ grid-template-columns: auto 1fr;
53
+
54
+ a {
55
+ color: ${theme.color.neutral.text};
56
+ text-decoration: underline;
57
+ ${theme.typography.shared.desktop.bodyMMedium};
45
58
  }
59
+
60
+ ${({ $hasCaption }) => ($hasCaption
61
+ ? css`
62
+ grid-template-areas:
63
+ 'iconArea captionArea'
64
+ 'descriptionArea descriptionArea';
65
+ ` : css`
66
+ grid-template-areas:
67
+ 'iconArea descriptionArea';
68
+ `)}
69
+ }
70
+
71
+
72
+ ${({ $boxless }) => (!$boxless && css`
73
+ /* the dismiss button is bigger than text so we apply smaller padding on wrapper & some on the content and icon */
74
+ padding: ${theme.space.space8} ${theme.space.space16};
75
+
76
+ .${messageClassNames.content} {
77
+ padding: ${theme.space.space4} 0;
78
+ }
79
+ `)}
80
+
81
+ .${messageClassNames.actionsWrapper} {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: ${theme.space.space8};
46
85
  }
47
86
 
48
87
  &.borderless {
@@ -55,28 +94,36 @@ const StyledMessage = styled(Box)`
55
94
  background-color: ${theme.color.neutral.background};
56
95
  border: 1px solid ${theme.color.neutral.border};
57
96
 
58
- .${messageClassNames.icon} { color: ${theme.color.primary.icon}; }
97
+ .${messageClassNames.icon} {
98
+ color: ${theme.color.neutral.icon};
99
+ }
59
100
  }
60
101
 
61
102
  &.success {
62
103
  background-color: ${theme.color.success.backgroundSubtle};
63
104
  border: 1px solid ${theme.color.success.borderSubtle};
64
105
 
65
- .${messageClassNames.icon} { color: ${theme.color.success.icon}; }
106
+ .${messageClassNames.icon} {
107
+ color: ${theme.color.success.icon};
108
+ }
66
109
  }
67
110
 
68
111
  &.warning {
69
112
  background-color: ${theme.color.warning.backgroundSubtle};
70
113
  border: 1px solid ${theme.color.warning.borderSubtle};
71
114
 
72
- .${messageClassNames.icon} { color: ${theme.color.warning.icon}; }
115
+ .${messageClassNames.icon} {
116
+ color: ${theme.color.warning.icon};
117
+ }
73
118
  }
74
119
 
75
120
  &.danger {
76
121
  background-color: ${theme.color.danger.backgroundSubtle};
77
122
  border: 1px solid ${theme.color.danger.borderSubtle};
78
123
 
79
- .${messageClassNames.icon} { color: ${theme.color.danger.icon}; }
124
+ .${messageClassNames.icon} {
125
+ color: ${theme.color.danger.icon};
126
+ }
80
127
  }
81
128
  `;
82
129
 
@@ -84,20 +131,27 @@ export type MessageType = 'info' | 'warning' | 'success' | 'danger';
84
131
 
85
132
  const typeToIcon: { [key in MessageType]: React.ElementType } = {
86
133
  info: InfoIcon,
87
- success: CheckCircleIcon,
134
+ success: CheckIcon,
88
135
  warning: WarningTriangleIcon,
89
- danger: WarningTriangleIcon,
136
+ danger: XCircleIcon,
137
+ };
138
+
139
+ type ActionButtonProps = Omit<ButtonProps, 'children'> & {
140
+ label: ReactNode,
90
141
  };
91
142
 
92
143
  type MessageProps = BoxProps & {
93
144
  type: MessageType,
94
145
  caption?: string,
95
- icon?: React.ElementType,
146
+ /** @deprecated Use `Icon` instead. */
147
+ icon?: IconComponentType,
148
+ Icon?: IconComponentType,
96
149
  onDismissClick?: () => void,
97
150
  borderless?: boolean,
98
151
  boxless?: boolean,
99
152
  as?: React.ElementType,
100
153
  dismissTrackingId?: string,
154
+ actions?: ActionButtonProps[],
101
155
  }
102
156
 
103
157
  /**
@@ -105,6 +159,7 @@ type MessageProps = BoxProps & {
105
159
  */
106
160
  export const Message: React.FC<MessageProps> = ({
107
161
  className,
162
+ Icon,
108
163
  icon,
109
164
  caption,
110
165
  children,
@@ -113,33 +168,56 @@ export const Message: React.FC<MessageProps> = ({
113
168
  borderless = false,
114
169
  boxless = false,
115
170
  dismissTrackingId,
171
+ actions = [],
116
172
  as,
117
173
  ...rest
118
174
  }) => {
119
- const Icon = icon || typeToIcon[type];
175
+ const IconComponent = icon || Icon || typeToIcon[type];
176
+ const hasCaption = !!caption && !!children;
177
+
120
178
  return (
121
179
  <StyledMessage
122
180
  className={clsx(className, messageClassNames.main, type, borderless && 'borderless')}
123
181
  mb={boxless ? 'space8' : 'space16'} // TODO: Export message without margin
124
- p={boxless ? 'none' : 'space16'}
125
182
  forwardedAs={as}
183
+ $boxless={boxless}
184
+ $hasCaption={hasCaption}
126
185
  {...rest}
127
186
  >
128
- <Icon className={messageClassNames.icon} size="20" />
129
187
  <div className={messageClassNames.content}>
130
- {caption && (<Heading as='div' type='titleM' className={messageClassNames.caption}>{caption}</Heading>)}
131
- {children}
188
+ <IconComponent className={messageClassNames.icon} size='16' />
189
+ {hasCaption && <Text weight='bold' className={messageClassNames.caption}>{caption}</Text>}
190
+ <div className={messageClassNames.description}>{children}</div>
132
191
  </div>
133
- {onDismissClick && (
134
- <Button
135
- size='small'
136
- variant='tertiary'
137
- trackingId={dismissTrackingId}
138
- onClick={onDismissClick}
139
- className={messageClassNames.dismiss}
140
- >
141
- <CrossIcon size="16" />
142
- </Button>
192
+ {(actions.length > 0 || onDismissClick) && (
193
+ <div className={messageClassNames.actionsWrapper}>
194
+ {actions.length > 0 && (
195
+ <>
196
+ {actions.map(({ label, ...action }, index) => (
197
+ <Button
198
+ key={index}
199
+ size='small'
200
+ variant='primary'
201
+ {...action}
202
+ >
203
+ {label}
204
+ </Button>
205
+ ))}
206
+ </>
207
+ )}
208
+ {onDismissClick && (
209
+ <Button
210
+ size='small'
211
+ variant='tertiary'
212
+ trackingId={dismissTrackingId}
213
+ onClick={onDismissClick}
214
+ className={messageClassNames.dismiss}
215
+ >
216
+ <CrossIcon size="16" color={theme.color.neutral.icon}/>
217
+ </Button>
218
+ )}
219
+
220
+ </div>
143
221
  )}
144
222
  </StyledMessage>
145
223
  );