@transferwise/components 46.40.0 → 46.41.1

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.
Files changed (37) hide show
  1. package/build/index.js +68 -74
  2. package/build/index.js.map +1 -1
  3. package/build/index.mjs +69 -75
  4. package/build/index.mjs.map +1 -1
  5. package/build/main.css +69 -14
  6. package/build/styles/main.css +69 -14
  7. package/build/styles/statusIcon/StatusIcon.css +4 -2
  8. package/build/styles/uploadInput/UploadInput.css +18 -1
  9. package/build/styles/uploadInput/uploadButton/UploadButton.css +4 -0
  10. package/build/styles/uploadInput/uploadItem/UploadItem.css +43 -11
  11. package/build/types/dateInput/DateInput.d.ts.map +1 -1
  12. package/build/types/field/Field.d.ts +6 -1
  13. package/build/types/field/Field.d.ts.map +1 -1
  14. package/build/types/inlineAlert/InlineAlert.d.ts.map +1 -1
  15. package/package.json +4 -4
  16. package/src/dateInput/DateInput.spec.tsx +220 -0
  17. package/src/dateInput/DateInput.story.tsx +3 -76
  18. package/src/dateInput/DateInput.tests.story.tsx +238 -0
  19. package/src/dateInput/DateInput.tsx +50 -53
  20. package/src/field/Field.story.tsx +17 -36
  21. package/src/field/Field.tests.story.tsx +33 -0
  22. package/src/field/Field.tsx +23 -13
  23. package/src/inlineAlert/InlineAlert.story.tsx +13 -5
  24. package/src/inlineAlert/InlineAlert.tsx +13 -6
  25. package/src/main.css +69 -14
  26. package/src/statusIcon/StatusIcon.css +4 -2
  27. package/src/statusIcon/StatusIcon.less +4 -2
  28. package/src/statusIcon/StatusIcon.tsx +1 -1
  29. package/src/uploadInput/UploadInput.css +18 -1
  30. package/src/uploadInput/UploadInput.less +17 -1
  31. package/src/uploadInput/UploadInput.tests.story.tsx +13 -2
  32. package/src/uploadInput/uploadButton/UploadButton.css +4 -0
  33. package/src/uploadInput/uploadButton/UploadButton.less +5 -0
  34. package/src/uploadInput/uploadItem/UploadItem.css +43 -11
  35. package/src/uploadInput/uploadItem/UploadItem.less +61 -17
  36. package/src/dateInput/DateInput.rtl.spec.tsx +0 -17
  37. package/src/dateInput/DateInput.spec.js +0 -477
@@ -2,8 +2,16 @@ import classNames from 'classnames';
2
2
  import { useState } from 'react';
3
3
  import { useIntl } from 'react-intl';
4
4
 
5
- import { Input, SelectInput, SelectInputOptionContent, SelectInputProps } from '..';
6
- import { DateMode, MonthFormat, Size, SizeLarge, SizeMedium, SizeSmall } from '../common';
5
+ import { Body, Input, SelectInput, SelectInputOptionContent, SelectInputProps } from '..';
6
+ import {
7
+ DateMode,
8
+ MonthFormat,
9
+ Size,
10
+ SizeLarge,
11
+ SizeMedium,
12
+ SizeSmall,
13
+ Typography,
14
+ } from '../common';
7
15
  import { MDY, YMD, getMonthNames, isDateValid, isMonthAndYearFormat } from '../common/dateUtils';
8
16
  import { useInputAttributes } from '../inputs/contexts';
9
17
  import messages from './DateInput.messages';
@@ -93,8 +101,10 @@ const DateInput = ({
93
101
  };
94
102
 
95
103
  const [day, setDay] = useState(() => getInitialDate('day'));
104
+ const [displayDay, setDisplayDay] = useState(day?.toString());
96
105
  const [month, setMonth] = useState(() => getInitialDate('month'));
97
106
  const [year, setYear] = useState(() => getInitialDate('year'));
107
+ const [displayYear, setDisplayYear] = useState(year?.toString());
98
108
  const [lastBroadcastedValue, setLastBroadcastedValue] = useState<Date | null | undefined>(
99
109
  getDateObject,
100
110
  );
@@ -129,7 +139,7 @@ const DateInput = ({
129
139
  const getSelectElement = () => {
130
140
  return (
131
141
  <label className="d-flex flex-column">
132
- <span className="sr-only">{monthLabel}</span>
142
+ <Body type={Typography.BODY_DEFAULT}>{monthLabel}</Body>
133
143
  <SelectInput
134
144
  name="month"
135
145
  disabled={disabled}
@@ -147,13 +157,25 @@ const DateInput = ({
147
157
  );
148
158
  };
149
159
 
160
+ const isDayValid = (newDay: number, newMonth: number, newYear: number) => {
161
+ const maxDay = new Date(newYear, newMonth + 1, 0).getDate();
162
+ return newDay <= maxDay;
163
+ };
164
+
150
165
  const handleInternalValue = (newDay = day, newMonth = month, newYear = year) => {
151
- if (newDay == null || newMonth == null || newYear == null) {
166
+ if (newDay == null || newDay === 0 || newMonth == null || newYear == null || newYear === 0) {
167
+ broadcastNewValue(null);
168
+ return;
169
+ }
170
+ if (!isDayValid(newDay, newMonth, newYear)) {
152
171
  broadcastNewValue(null);
153
172
  return;
154
173
  }
155
174
 
156
175
  const dateValue = new Date(newYear, newMonth, newDay);
176
+ if (newYear < 100) {
177
+ dateValue.setFullYear(newYear);
178
+ }
157
179
 
158
180
  if (!isDateValid(dateValue)) {
159
181
  broadcastNewValue(null);
@@ -170,9 +192,12 @@ const DateInput = ({
170
192
  };
171
193
 
172
194
  const handleDayChange = (event: React.ChangeEvent<HTMLInputElement>) => {
173
- const { checkedDay } = checkDate(Number.parseInt(event.target.value, 10), month, year);
174
- setDay(checkedDay);
175
- handleInternalValue(checkedDay, month, year);
195
+ const newDayString = event.target.value.replace(/\D/g, '');
196
+ const newDayNumber = Number.parseInt(newDayString, 10);
197
+
198
+ setDay(newDayNumber);
199
+ setDisplayDay(newDayString);
200
+ handleInternalValue(newDayNumber, month, year);
176
201
  };
177
202
 
178
203
  const handleMonthChange = (selectedMonth: number | null) => {
@@ -181,30 +206,21 @@ const DateInput = ({
181
206
  handleInternalValue(day, null, year);
182
207
  return;
183
208
  }
184
- const { checkedDay } = checkDate(day, selectedMonth, year);
185
209
  setMonth(selectedMonth);
186
- if (day && checkedDay !== day) {
187
- setDay(checkedDay);
188
- }
189
- handleInternalValue(checkedDay, selectedMonth, year);
210
+ handleInternalValue(day, selectedMonth, year);
190
211
  };
191
212
 
192
213
  const handleYearChange = (event: React.ChangeEvent<HTMLInputElement>) => {
193
- const newValue = event.target.value;
194
- const slicedYear = newValue.length > 4 ? newValue.slice(0, 4) : newValue;
195
-
196
- if (slicedYear.toString().length === 4) {
197
- // Correct day based on year and month.
198
- const { checkedDay } = checkDate(day, month, Number.parseInt(newValue, 10));
199
-
200
- if (day && checkedDay !== day) {
201
- setDay(checkedDay);
202
- }
214
+ const newYearString = event.target.value.replace(/\D/g, '');
215
+ const newYearNumber = Number.parseInt(newYearString, 10);
203
216
 
204
- setYear(Number.parseInt(slicedYear, 10));
205
- handleInternalValue(checkedDay, month, Number.parseInt(slicedYear, 10));
217
+ if (newYearString.length >= 4 && newYearString.length <= 6) {
218
+ setYear(newYearNumber);
219
+ setDisplayYear(newYearString);
220
+ handleInternalValue(day, month, newYearNumber);
206
221
  } else {
207
- setYear(Number.parseInt(slicedYear, 10));
222
+ setYear(null);
223
+ setDisplayYear(newYearString);
208
224
  handleInternalValue(day, month, null);
209
225
  }
210
226
  };
@@ -216,29 +232,6 @@ const DateInput = ({
216
232
  }
217
233
  };
218
234
 
219
- const checkDate = (
220
- newDay: number | null = null,
221
- newMonth: number | null = 0,
222
- newYear: number | null = null,
223
- ) => {
224
- let checkedDay = newDay;
225
- const maxDay = new Date(newYear || 2000, newMonth != null ? newMonth + 1 : 1, 0).getDate();
226
-
227
- if (!newDay) {
228
- checkedDay = null;
229
- }
230
-
231
- if (newDay && newDay < 0) {
232
- checkedDay = 1;
233
- }
234
-
235
- if ((newDay && newMonth) || (newDay && newDay > 31)) {
236
- checkedDay = newDay > maxDay ? maxDay : newDay;
237
- }
238
-
239
- return { checkedDay, checkedMonth: newMonth, checkedYear: newYear };
240
- };
241
-
242
235
  const monthYearOnly = mode === DateMode.MONTH_YEAR;
243
236
 
244
237
  const monthWidth = classNames({
@@ -254,7 +247,7 @@ const DateInput = ({
254
247
  return (
255
248
  <div className="col-sm-3">
256
249
  <label>
257
- <span className="sr-only">{dayLabel}</span>
250
+ <Body type={Typography.BODY_DEFAULT}>{dayLabel}</Body>
258
251
  <div className={`input-group input-group-${size}`}>
259
252
  <Input
260
253
  type="text"
@@ -262,10 +255,12 @@ const DateInput = ({
262
255
  pattern="[0-9]*"
263
256
  name="day"
264
257
  autoComplete={dayAutoComplete}
265
- value={day || ''}
258
+ value={displayDay || ''}
266
259
  placeholder={placeholders?.day}
267
260
  disabled={disabled}
268
261
  min={1}
262
+ max={31}
263
+ maxLength={2}
269
264
  onChange={(event) => handleDayChange(event)}
270
265
  />
271
266
  </div>
@@ -278,7 +273,7 @@ const DateInput = ({
278
273
  return (
279
274
  <div className="col-sm-4">
280
275
  <label>
281
- <span className="sr-only">{yearLabel}</span>
276
+ <Body type={Typography.BODY_DEFAULT}>{yearLabel}</Body>
282
277
  <div className={`input-group input-group-${size}`}>
283
278
  <Input
284
279
  type="text"
@@ -287,9 +282,11 @@ const DateInput = ({
287
282
  name="year"
288
283
  autoComplete={yearAutoComplete}
289
284
  placeholder={placeholders?.year}
290
- value={year || ''}
285
+ value={displayYear || ''}
291
286
  disabled={disabled}
292
- min={1}
287
+ min={0}
288
+ max={9999}
289
+ maxLength={6}
293
290
  onChange={(event) => handleYearChange(event)}
294
291
  />
295
292
  </div>
@@ -2,10 +2,12 @@ import { useState } from 'react';
2
2
 
3
3
  import { Input } from '../inputs/Input';
4
4
  import { Field } from './Field';
5
+ import { Sentiment } from '../common';
5
6
 
6
7
  export default {
7
8
  component: Field,
8
9
  title: 'Field',
10
+ tags: ['autodocs'],
9
11
  };
10
12
 
11
13
  export const Basic = () => {
@@ -17,43 +19,22 @@ export const Basic = () => {
17
19
  );
18
20
  };
19
21
 
20
- export const WithErrorMessage = () => {
22
+ export const WithStatusMessages = () => {
21
23
  const [value, setValue] = useState<string | undefined>('This is some text');
22
24
  return (
23
- <Field label="Phone number" error="This is a required field">
24
- <Input value={value} onChange={({ target }) => setValue(target.value)} />
25
- </Field>
26
- );
27
- };
28
-
29
- export const WithHelp = () => {
30
- const [value, setValue] = useState<string | undefined>('This is some text');
31
- return (
32
- <Field label="Phone number" hint="This is a helpful message">
33
- <Input value={value} onChange={({ target }) => setValue(target.value)} />
34
- </Field>
35
- );
36
- };
37
-
38
- export const WithHelpAndErrorOnBlur = () => {
39
- const [value, setValue] = useState<string | undefined>('This is some text');
40
- const [error, setError] = useState<string | undefined>(undefined);
41
- return (
42
- <Field label="Phone number" hint="Please include country code" error={error}>
43
- <Input
44
- value={value}
45
- onChange={({ target }) => {
46
- setValue(target.value);
47
- setError(undefined);
48
- }}
49
- onBlur={() => {
50
- if (!value) {
51
- setError('This is a required field');
52
- } else {
53
- setError(undefined);
54
- }
55
- }}
56
- />
57
- </Field>
25
+ <div>
26
+ <Field label="Phone number" sentiment={Sentiment.POSITIVE} message="Positive message">
27
+ <Input value={value} onChange={({ target }) => setValue(target.value)} />
28
+ </Field>
29
+ <Field label="Phone number" sentiment={Sentiment.WARNING} message="Warning message">
30
+ <Input value={value} onChange={({ target }) => setValue(target.value)} />
31
+ </Field>
32
+ <Field label="Phone number" sentiment={Sentiment.NEGATIVE} message="This is a required field">
33
+ <Input value={value} onChange={({ target }) => setValue(target.value)} />
34
+ </Field>
35
+ <Field label="Phone number" message="This is a helpful message">
36
+ <Input value={value} onChange={({ target }) => setValue(target.value)} />
37
+ </Field>
38
+ </div>
58
39
  );
59
40
  };
@@ -0,0 +1,33 @@
1
+ import { useState } from 'react';
2
+
3
+ import { Input } from '../inputs/Input';
4
+ import { Field } from './Field';
5
+ import { Sentiment } from '../common';
6
+
7
+ export default {
8
+ component: Field,
9
+ title: 'Field/Tests',
10
+ };
11
+
12
+ export const WithHelpAndErrorOnBlur = () => {
13
+ const [value, setValue] = useState('This is some text');
14
+ const [error, setError] = useState<string | undefined>(undefined);
15
+ return (
16
+ <Field
17
+ label="Phone number"
18
+ sentiment={error ? Sentiment.NEGATIVE : Sentiment.NEUTRAL}
19
+ message={error || 'Please include country code'}
20
+ >
21
+ <Input
22
+ value={value}
23
+ onChange={({ target }) => {
24
+ setValue(target.value);
25
+ setError(undefined);
26
+ }}
27
+ onBlur={() => {
28
+ setError('Something went wrong');
29
+ }}
30
+ />
31
+ </Field>
32
+ );
33
+ };
@@ -15,15 +15,28 @@ export type FieldProps = {
15
15
  /** `null` disables auto-generating the `id` attribute, falling back to nesting-based label association over setting `htmlFor` explicitly. */
16
16
  id?: string | null;
17
17
  label: React.ReactNode;
18
+ /** @deprecated use `message` and `type={Sentiment.NEUTRAL}` prop instead */
18
19
  hint?: React.ReactNode;
20
+ message?: React.ReactNode;
21
+ /** @deprecated use `message` and `type={Sentiment.NEGATIVE}` prop instead */
19
22
  error?: React.ReactNode;
23
+ sentiment?: `${Sentiment.NEGATIVE | Sentiment.NEUTRAL | Sentiment.POSITIVE | Sentiment.WARNING}`;
20
24
  className?: string;
21
25
  children?: React.ReactNode;
22
26
  };
23
27
 
24
- export const Field = ({ id, label, hint, error, className, children }: FieldProps) => {
25
- const hasError = Boolean(error);
26
- const hasHint = Boolean(hint) && !hasError;
28
+ export const Field = ({
29
+ id,
30
+ label,
31
+ message: propMessage,
32
+ sentiment: propType = Sentiment.NEUTRAL,
33
+ className,
34
+ children,
35
+ ...props
36
+ }: FieldProps) => {
37
+ const sentiment = props.error ? Sentiment.NEGATIVE : propType;
38
+ const message = props.error || props.hint || propMessage;
39
+ const hasError = sentiment === Sentiment.NEGATIVE;
27
40
 
28
41
  const labelId = useId();
29
42
 
@@ -35,14 +48,16 @@ export const Field = ({ id, label, hint, error, className, children }: FieldProp
35
48
  return (
36
49
  <FieldLabelIdContextProvider value={labelId}>
37
50
  <InputIdContextProvider value={inputId}>
38
- <InputDescribedByProvider value={hasError || hasHint ? descriptionId : undefined}>
51
+ <InputDescribedByProvider value={message ? descriptionId : undefined}>
39
52
  <InputInvalidProvider value={hasError}>
40
53
  <div
41
54
  className={classNames(
42
55
  'form-group d-block',
43
56
  {
57
+ 'has-success': sentiment === Sentiment.POSITIVE,
58
+ 'has-warning': sentiment === Sentiment.WARNING,
44
59
  'has-error': hasError,
45
- 'has-info': hasHint,
60
+ 'has-info': sentiment === Sentiment.NEUTRAL,
46
61
  },
47
62
  className,
48
63
  )}
@@ -51,14 +66,9 @@ export const Field = ({ id, label, hint, error, className, children }: FieldProp
51
66
  {label}
52
67
  {children}
53
68
  </Label>
54
- {hasHint && (
55
- <InlineAlert type={Sentiment.NEUTRAL} id={descriptionId}>
56
- {hint}
57
- </InlineAlert>
58
- )}
59
- {hasError && (
60
- <InlineAlert type={Sentiment.NEGATIVE} id={descriptionId}>
61
- {error}
69
+ {message && (
70
+ <InlineAlert type={sentiment} id={descriptionId}>
71
+ {message}
62
72
  </InlineAlert>
63
73
  )}
64
74
  </div>
@@ -51,7 +51,7 @@ export const Basic = () => {
51
51
 
52
52
  return (
53
53
  <>
54
- {/*eslint-disable-next-line react/no-adjacent-inline-elements */}
54
+ {/* eslint-disable-next-line react/no-adjacent-inline-elements */}
55
55
  <p>
56
56
  The styling for the input (the coloured border) and the visibility of the inline alert is
57
57
  controlled through the use of <code>has-***</code> classes which are applied to the{' '}
@@ -60,7 +60,11 @@ export const Basic = () => {
60
60
  element. The available classes are <code>has-error</code>, <code>has-info</code>,{' '}
61
61
  <code>has-warning</code> and <code>has-success</code>.
62
62
  </p>
63
- <p>Where possible consumers should use Field instead of doing this manually.</p>
63
+ <p>
64
+ Where possible consumers should use{' '}
65
+ <a href="https://storybook.wise.design/?path=/story/field--basic">Field</a> instead of doing
66
+ this manually.
67
+ </p>
64
68
  <div className={`form-group ${typeClass}`}>
65
69
  <label className="control-label" htmlFor="id0">
66
70
  Toggleable
@@ -73,14 +77,18 @@ export const Basic = () => {
73
77
  Negative
74
78
  </label>
75
79
  <Input id="id1" value="Neptune is cool" />
76
- <InlineAlert type="error">{message}</InlineAlert>
80
+ <InlineAlert type="negative">{message}</InlineAlert>
77
81
  </div>
78
- <div className="form-group has-positive">
82
+ <div className="form-group has-success">
79
83
  <label className="control-label" htmlFor="id2">
80
84
  Positive
81
85
  </label>
82
86
  <Input id="id2" value="Neptune is cool" />
83
- <InlineAlert type="positive">{message}</InlineAlert>
87
+ <InlineAlert type="positive">
88
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
89
+ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
90
+ ullamco laboris nisi ut aliquip ex ea commodo consequat.
91
+ </InlineAlert>
84
92
  </div>
85
93
  <div className="form-group has-neutral">
86
94
  <label className="control-label" htmlFor="id3">
@@ -1,8 +1,8 @@
1
- import { AlertCircle as AlertCircleIcon } from '@transferwise/icons';
2
1
  import classNames from 'classnames';
3
2
  import { ReactNode } from 'react';
4
3
 
5
- import { Sentiment } from '../common';
4
+ import { Sentiment, Size } from '../common';
5
+ import StatusIcon from '../statusIcon';
6
6
 
7
7
  export interface InlineAlertProps {
8
8
  id?: string;
@@ -17,15 +17,22 @@ export default function InlineAlert({
17
17
  className,
18
18
  children,
19
19
  }: InlineAlertProps) {
20
- const danger = type === 'negative' || type === 'error';
21
20
  return (
22
21
  <div
23
22
  role="alert"
24
23
  id={id}
25
- className={classNames('alert alert-detach', `alert-${danger ? 'danger' : type}`, className)}
24
+ className={classNames(
25
+ 'alert alert-detach',
26
+ `alert-${type === Sentiment.NEGATIVE || type === Sentiment.ERROR ? 'danger' : type}`,
27
+ className,
28
+ )}
26
29
  >
27
- {danger && <AlertCircleIcon />}
28
- <div>{children}</div>
30
+ <div className="d-inline-flex">
31
+ {type !== Sentiment.NEUTRAL && type !== Sentiment.PENDING && (
32
+ <StatusIcon sentiment={type} size={Size.SMALL} />
33
+ )}
34
+ <div className="np-text-body-default">{children}</div>
35
+ </div>
29
36
  </div>
30
37
  );
31
38
  }
package/src/main.css CHANGED
@@ -4208,14 +4208,16 @@ html:not([dir="rtl"]) .np-navigation-option {
4208
4208
  height: var(--size-32);
4209
4209
  }
4210
4210
  }
4211
- .status-circle.negative {
4211
+ .status-circle.negative,
4212
+ .status-circle.error {
4212
4213
  background-color: var(--color-sentiment-negative);
4213
4214
  }
4214
4215
  .status-circle.neutral {
4215
4216
  background-color: #5d7079;
4216
4217
  background-color: var(--color-content-secondary);
4217
4218
  }
4218
- .status-circle.positive {
4219
+ .status-circle.positive,
4220
+ .status-circle.success {
4219
4221
  background-color: var(--color-sentiment-positive);
4220
4222
  }
4221
4223
  .tw-stepper {
@@ -5400,6 +5402,9 @@ html:not([dir="rtl"]) .np-navigation-option {
5400
5402
  border-color: #c9cbce !important;
5401
5403
  border-color: var(--color-interactive-secondary) !important;
5402
5404
  }
5405
+ .np-upload-button-container.droppable-dropping:before {
5406
+ z-index: 2;
5407
+ }
5403
5408
  .np-upload-button-container input[type="file"] {
5404
5409
  opacity: 0;
5405
5410
  z-index: -1;
@@ -5410,6 +5415,7 @@ html:not([dir="rtl"]) .np-navigation-option {
5410
5415
  }
5411
5416
  .np-upload-button {
5412
5417
  width: 100%;
5418
+ border-top: 1px solid transparent;
5413
5419
  padding: 16px;
5414
5420
  padding: var(--padding-small);
5415
5421
  border-radius: 0;
@@ -5477,8 +5483,25 @@ label.np-upload-button:not(.disabled):active {
5477
5483
  color: var(--color-sentiment-negative) !important;
5478
5484
  }
5479
5485
  .np-theme-personal .np-upload-input-errors {
5486
+ list-style: none;
5480
5487
  padding-left: 0;
5481
- list-style-position: inside;
5488
+ }
5489
+ .np-theme-personal .np-upload-input-errors li {
5490
+ position: relative;
5491
+ padding-left: 16px;
5492
+ padding-left: var(--size-16);
5493
+ }
5494
+ @media (max-width: 320px) {
5495
+ .np-theme-personal .np-upload-input-errors li {
5496
+ padding-left: 32px;
5497
+ padding-left: var(--size-32);
5498
+ }
5499
+ }
5500
+ .np-theme-personal .np-upload-input-errors li:before {
5501
+ content: '•';
5502
+ position: absolute;
5503
+ display: block;
5504
+ left: 0;
5482
5505
  }
5483
5506
  .np-theme-personal .np-upload-input .status-circle {
5484
5507
  width: 24px;
@@ -5497,30 +5520,49 @@ label.np-upload-button:not(.disabled):active {
5497
5520
  .np-upload-item {
5498
5521
  border: 1px solid #c9cbce;
5499
5522
  border: 1px solid var(--color-interactive-secondary);
5523
+ position: relative;
5500
5524
  }
5501
- .np-upload-item:first-child ~ div:before {
5525
+ .np-upload-item:first-child ~ div:not(.np-upload-item__link):before,
5526
+ .np-upload-item:not(:first-child).np-upload-item__link .np-upload-item--link:before,
5527
+ .np-upload-item.np-upload-item__link:hover .np-upload-item--link:after {
5502
5528
  display: block;
5503
- position: relative;
5529
+ position: absolute;
5504
5530
  height: 1px;
5505
5531
  background-color: rgba(0,0,0,0.10196);
5506
5532
  background-color: var(--color-border-neutral);
5507
5533
  content: " ";
5508
- margin: 0 16px;
5509
- margin: 0 var(--size-16);
5534
+ left: 16px;
5535
+ left: var(--size-16);
5536
+ width: calc(100% - 2 * 16px);
5537
+ width: calc(100% - 2 * var(--size-16));
5538
+ }
5539
+ .np-upload-item:first-child ~ div:not(.np-upload-item__link):before,
5540
+ .np-upload-item:not(:first-child).np-upload-item__link .np-upload-item--link:before {
5541
+ top: 0;
5542
+ }
5543
+ .np-upload-item.np-upload-item__link:hover .np-upload-item--link:after {
5544
+ bottom: -1px;
5510
5545
  }
5511
5546
  .np-upload-item:first-child ~ div {
5512
- border-top: 0;
5547
+ border-top: 1px;
5548
+ }
5549
+ .np-upload-item:not(:first-child) .np-upload-item--link:hover {
5550
+ border-top-color: rgba(0,0,0,0.10196);
5551
+ border-top-color: var(--color-border-neutral);
5513
5552
  }
5514
5553
  .np-upload-item:not(:last-child) {
5515
5554
  border-bottom: 0;
5516
5555
  }
5517
- .np-upload-item.np-upload-item__link:hover:before,
5518
- .np-upload-button-container:hover:before {
5519
- margin: 0 !important;
5556
+ .np-upload-item.np-upload-item__link:hover + .np-upload-item:before,
5557
+ .np-upload-item.np-upload-item__link:hover + .np-upload-button-container:before,
5558
+ .np-upload-item.np-upload-item__link:hover + .np-upload-item .np-upload-item--link:before,
5559
+ .np-upload-item.np-upload-item__link:hover + .np-upload-button-container .np-upload-item--link:before {
5560
+ display: none;
5520
5561
  }
5521
- .np-upload-item.np-upload-item__link:hover + div:before,
5522
- .np-upload-button-container:hover + div:before {
5523
- margin: 0 !important;
5562
+ .np-upload-button-container:hover:before,
5563
+ .np-upload-button-container.droppable-dropping:before {
5564
+ left: 0 !important;
5565
+ width: 100% !important;
5524
5566
  }
5525
5567
  .np-upload-button-container:has(:focus-visible) {
5526
5568
  outline: var(--ring-outline-color) solid var(--ring-outline-width);
@@ -5537,17 +5579,29 @@ label.np-upload-button:not(.disabled):active {
5537
5579
  flex: 1;
5538
5580
  -webkit-text-decoration: none;
5539
5581
  text-decoration: none;
5582
+ border-top: 1px solid transparent;
5540
5583
  border-radius: inherit;
5541
5584
  }
5542
5585
  .np-upload-item__link a:focus-visible {
5543
5586
  outline-offset: -2px;
5544
5587
  }
5588
+ .np-upload-item__link a:hover:before {
5589
+ display: none !important;
5590
+ }
5591
+ .np-upload-item__link a:hover:after {
5592
+ left: 0 !important;
5593
+ width: 100% !important;
5594
+ }
5545
5595
  .np-upload-item__link a:hover,
5546
5596
  .np-upload-item__link a:active {
5547
5597
  -webkit-text-decoration: none;
5548
5598
  text-decoration: none;
5599
+ }
5600
+ .np-upload-item__link a:hover .np-upload-button,
5601
+ .np-upload-item__link a:active .np-upload-button {
5549
5602
  background-color: rgba(134,167,189,0.10196);
5550
5603
  background-color: var(--color-background-neutral);
5604
+ border-radius: inherit;
5551
5605
  }
5552
5606
  .np-upload-item__body {
5553
5607
  display: flex;
@@ -5572,6 +5626,7 @@ label.np-upload-button:not(.disabled):active {
5572
5626
  outline-offset: 0 !important;
5573
5627
  background-color: rgba(134,167,189,0.10196);
5574
5628
  background-color: var(--color-background-neutral);
5629
+ border: none;
5575
5630
  color: var(--color-interactive-primary);
5576
5631
  right: 16px;
5577
5632
  right: var(--size-16);
@@ -66,13 +66,15 @@
66
66
  height: var(--size-32);
67
67
  }
68
68
  }
69
- .status-circle.negative {
69
+ .status-circle.negative,
70
+ .status-circle.error {
70
71
  background-color: var(--color-sentiment-negative);
71
72
  }
72
73
  .status-circle.neutral {
73
74
  background-color: #5d7079;
74
75
  background-color: var(--color-content-secondary);
75
76
  }
76
- .status-circle.positive {
77
+ .status-circle.positive,
78
+ .status-circle.success {
77
79
  background-color: var(--color-sentiment-positive);
78
80
  }
@@ -60,7 +60,8 @@
60
60
  }
61
61
  }
62
62
 
63
- .status-circle.negative {
63
+ .status-circle.negative,
64
+ .status-circle.error {
64
65
  background-color: var(--color-sentiment-negative);
65
66
  }
66
67
 
@@ -68,6 +69,7 @@
68
69
  background-color: var(--color-content-secondary);
69
70
  }
70
71
 
71
- .status-circle.positive {
72
+ .status-circle.positive,
73
+ .status-circle.success {
72
74
  background-color: var(--color-sentiment-positive);
73
75
  }
@@ -26,7 +26,7 @@ const StatusIcon = ({ sentiment = 'neutral', size = 'md' }: StatusIconProps) =>
26
26
  return (
27
27
  <span
28
28
  data-testid="status-icon"
29
- className={classNames('status-circle', 'status-circle-' + size, sentiment)}
29
+ className={classNames('status-circle', `status-circle-${size}`, sentiment)}
30
30
  >
31
31
  <Icon className={classNames('status-icon', iconColor)} />
32
32
  </span>