@snack-uikit/toaster 0.8.19 → 0.9.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.
Files changed (124) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +74 -37
  3. package/dist/cjs/components/ToastSystemEvent/styles.module.css +2 -3
  4. package/dist/cjs/components/ToastUpload/ToastUpload.d.ts +59 -0
  5. package/dist/cjs/components/ToastUpload/ToastUpload.js +132 -0
  6. package/dist/cjs/components/ToastUpload/constants.d.ts +16 -0
  7. package/dist/cjs/components/ToastUpload/constants.js +26 -0
  8. package/dist/cjs/components/ToastUpload/helperComponents/ButtonIcon/ButtonIcon.d.ts +3 -0
  9. package/dist/cjs/components/ToastUpload/helperComponents/ButtonIcon/ButtonIcon.js +32 -0
  10. package/dist/cjs/components/ToastUpload/helperComponents/ButtonIcon/index.d.ts +1 -0
  11. package/dist/cjs/components/ToastUpload/helperComponents/ButtonIcon/index.js +25 -0
  12. package/dist/cjs/components/ToastUpload/helperComponents/ButtonIcon/styles.module.css +32 -0
  13. package/dist/cjs/components/ToastUpload/helperComponents/ButtonTextNeutral/ButtonTextNeutral.d.ts +7 -0
  14. package/dist/cjs/components/ToastUpload/helperComponents/ButtonTextNeutral/ButtonTextNeutral.js +28 -0
  15. package/dist/cjs/components/ToastUpload/helperComponents/ButtonTextNeutral/index.d.ts +1 -0
  16. package/dist/cjs/components/ToastUpload/helperComponents/ButtonTextNeutral/index.js +25 -0
  17. package/dist/cjs/components/ToastUpload/helperComponents/ButtonTextNeutral/styles.module.css +33 -0
  18. package/dist/cjs/components/ToastUpload/helperComponents/FileItem/FileItem.d.ts +5 -0
  19. package/dist/cjs/components/ToastUpload/helperComponents/FileItem/FileItem.js +90 -0
  20. package/dist/cjs/components/ToastUpload/helperComponents/FileItem/index.d.ts +1 -0
  21. package/dist/cjs/components/ToastUpload/helperComponents/FileItem/index.js +25 -0
  22. package/dist/cjs/components/ToastUpload/helperComponents/FileItem/styles.module.css +87 -0
  23. package/dist/cjs/components/ToastUpload/helperComponents/LoadingStatus/LoadingStatus.d.ts +7 -0
  24. package/dist/cjs/components/ToastUpload/helperComponents/LoadingStatus/LoadingStatus.js +43 -0
  25. package/dist/cjs/components/ToastUpload/helperComponents/LoadingStatus/index.d.ts +1 -0
  26. package/dist/cjs/components/ToastUpload/helperComponents/LoadingStatus/index.js +25 -0
  27. package/dist/cjs/components/ToastUpload/helperComponents/LoadingStatus/styles.module.css +3 -0
  28. package/dist/cjs/components/ToastUpload/index.d.ts +1 -0
  29. package/dist/cjs/components/ToastUpload/index.js +25 -0
  30. package/dist/cjs/components/ToastUpload/styles.module.css +111 -0
  31. package/dist/cjs/components/ToastUpload/types.d.ts +55 -0
  32. package/dist/cjs/components/ToastUpload/types.js +5 -0
  33. package/dist/cjs/components/ToastUpload/utils/formatPercent.d.ts +1 -0
  34. package/dist/cjs/components/ToastUpload/utils/formatPercent.js +8 -0
  35. package/dist/cjs/components/ToastUpload/utils/index.d.ts +1 -0
  36. package/dist/cjs/components/ToastUpload/utils/index.js +25 -0
  37. package/dist/cjs/components/ToasterContainer/styles.module.css +9 -9
  38. package/dist/cjs/components/index.d.ts +1 -0
  39. package/dist/cjs/components/index.js +2 -1
  40. package/dist/cjs/constants.d.ts +2 -4
  41. package/dist/cjs/constants.js +10 -2
  42. package/dist/cjs/helperComponents/ButtonAction/styles.module.css +2 -2
  43. package/dist/cjs/helpers.d.ts +4 -0
  44. package/dist/cjs/helpers.js +42 -1
  45. package/dist/cjs/index.d.ts +2 -2
  46. package/dist/cjs/index.js +20 -0
  47. package/dist/cjs/testIds.d.ts +14 -0
  48. package/dist/cjs/testIds.js +16 -1
  49. package/dist/cjs/types.d.ts +9 -1
  50. package/dist/esm/components/ToastSystemEvent/styles.module.css +2 -3
  51. package/dist/esm/components/ToastUpload/ToastUpload.d.ts +59 -0
  52. package/dist/esm/components/ToastUpload/ToastUpload.js +45 -0
  53. package/dist/esm/components/ToastUpload/constants.d.ts +16 -0
  54. package/dist/esm/components/ToastUpload/constants.js +20 -0
  55. package/dist/esm/components/ToastUpload/helperComponents/ButtonIcon/ButtonIcon.d.ts +3 -0
  56. package/dist/esm/components/ToastUpload/helperComponents/ButtonIcon/ButtonIcon.js +18 -0
  57. package/dist/esm/components/ToastUpload/helperComponents/ButtonIcon/index.d.ts +1 -0
  58. package/dist/esm/components/ToastUpload/helperComponents/ButtonIcon/index.js +1 -0
  59. package/dist/esm/components/ToastUpload/helperComponents/ButtonIcon/styles.module.css +32 -0
  60. package/dist/esm/components/ToastUpload/helperComponents/ButtonTextNeutral/ButtonTextNeutral.d.ts +7 -0
  61. package/dist/esm/components/ToastUpload/helperComponents/ButtonTextNeutral/ButtonTextNeutral.js +5 -0
  62. package/dist/esm/components/ToastUpload/helperComponents/ButtonTextNeutral/index.d.ts +1 -0
  63. package/dist/esm/components/ToastUpload/helperComponents/ButtonTextNeutral/index.js +1 -0
  64. package/dist/esm/components/ToastUpload/helperComponents/ButtonTextNeutral/styles.module.css +33 -0
  65. package/dist/esm/components/ToastUpload/helperComponents/FileItem/FileItem.d.ts +5 -0
  66. package/dist/esm/components/ToastUpload/helperComponents/FileItem/FileItem.js +26 -0
  67. package/dist/esm/components/ToastUpload/helperComponents/FileItem/index.d.ts +1 -0
  68. package/dist/esm/components/ToastUpload/helperComponents/FileItem/index.js +1 -0
  69. package/dist/esm/components/ToastUpload/helperComponents/FileItem/styles.module.css +87 -0
  70. package/dist/esm/components/ToastUpload/helperComponents/LoadingStatus/LoadingStatus.d.ts +7 -0
  71. package/dist/esm/components/ToastUpload/helperComponents/LoadingStatus/LoadingStatus.js +16 -0
  72. package/dist/esm/components/ToastUpload/helperComponents/LoadingStatus/index.d.ts +1 -0
  73. package/dist/esm/components/ToastUpload/helperComponents/LoadingStatus/index.js +1 -0
  74. package/dist/esm/components/ToastUpload/helperComponents/LoadingStatus/styles.module.css +3 -0
  75. package/dist/esm/components/ToastUpload/index.d.ts +1 -0
  76. package/dist/esm/components/ToastUpload/index.js +1 -0
  77. package/dist/esm/components/ToastUpload/styles.module.css +111 -0
  78. package/dist/esm/components/ToastUpload/types.d.ts +55 -0
  79. package/dist/esm/components/ToastUpload/types.js +1 -0
  80. package/dist/esm/components/ToastUpload/utils/formatPercent.d.ts +1 -0
  81. package/dist/esm/components/ToastUpload/utils/formatPercent.js +1 -0
  82. package/dist/esm/components/ToastUpload/utils/index.d.ts +1 -0
  83. package/dist/esm/components/ToastUpload/utils/index.js +1 -0
  84. package/dist/esm/components/ToasterContainer/styles.module.css +9 -9
  85. package/dist/esm/components/index.d.ts +1 -0
  86. package/dist/esm/components/index.js +1 -0
  87. package/dist/esm/constants.d.ts +2 -4
  88. package/dist/esm/constants.js +8 -0
  89. package/dist/esm/helperComponents/ButtonAction/styles.module.css +2 -2
  90. package/dist/esm/helpers.d.ts +4 -0
  91. package/dist/esm/helpers.js +42 -1
  92. package/dist/esm/index.d.ts +2 -2
  93. package/dist/esm/index.js +1 -0
  94. package/dist/esm/testIds.d.ts +14 -0
  95. package/dist/esm/testIds.js +15 -0
  96. package/dist/esm/types.d.ts +9 -1
  97. package/package.json +10 -6
  98. package/src/components/ToastSystemEvent/styles.module.scss +2 -2
  99. package/src/components/ToastUpload/ToastUpload.tsx +182 -0
  100. package/src/components/ToastUpload/constants.ts +26 -0
  101. package/src/components/ToastUpload/helperComponents/ButtonIcon/ButtonIcon.tsx +10 -0
  102. package/src/components/ToastUpload/helperComponents/ButtonIcon/index.ts +1 -0
  103. package/src/components/ToastUpload/helperComponents/ButtonIcon/styles.module.scss +43 -0
  104. package/src/components/ToastUpload/helperComponents/ButtonTextNeutral/ButtonTextNeutral.tsx +23 -0
  105. package/src/components/ToastUpload/helperComponents/ButtonTextNeutral/index.ts +1 -0
  106. package/src/components/ToastUpload/helperComponents/ButtonTextNeutral/styles.module.scss +38 -0
  107. package/src/components/ToastUpload/helperComponents/FileItem/FileItem.tsx +83 -0
  108. package/src/components/ToastUpload/helperComponents/FileItem/index.ts +1 -0
  109. package/src/components/ToastUpload/helperComponents/FileItem/styles.module.scss +91 -0
  110. package/src/components/ToastUpload/helperComponents/LoadingStatus/LoadingStatus.tsx +39 -0
  111. package/src/components/ToastUpload/helperComponents/LoadingStatus/index.ts +1 -0
  112. package/src/components/ToastUpload/helperComponents/LoadingStatus/styles.module.scss +5 -0
  113. package/src/components/ToastUpload/index.ts +1 -0
  114. package/src/components/ToastUpload/styles.module.scss +121 -0
  115. package/src/components/ToastUpload/types.ts +62 -0
  116. package/src/components/ToastUpload/utils/formatPercent.ts +1 -0
  117. package/src/components/ToastUpload/utils/index.ts +1 -0
  118. package/src/components/ToasterContainer/styles.module.scss +1 -1
  119. package/src/components/index.ts +1 -0
  120. package/src/constants.ts +9 -1
  121. package/src/helpers.tsx +38 -0
  122. package/src/index.ts +2 -10
  123. package/src/testIds.ts +17 -0
  124. package/src/types.ts +7 -1
@@ -0,0 +1,182 @@
1
+ import cn from 'classnames';
2
+ import { MouseEvent } from 'react';
3
+ import { ToastContentProps as RtToastContentProps } from 'react-toastify';
4
+ import { useUncontrolledProp } from 'uncontrollable';
5
+
6
+ import { ChevronDownSVG, ChevronUpSVG, CrossSVG } from '@snack-uikit/icons';
7
+ import { useLocale } from '@snack-uikit/locale';
8
+ import { ProgressBar } from '@snack-uikit/progress-bar';
9
+ import { Scroll } from '@snack-uikit/scroll';
10
+ import { TruncateString } from '@snack-uikit/truncate-string';
11
+ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
12
+
13
+ import { TOAST_UPLOAD_TEST_IDS } from '../../testIds';
14
+ import { progressBarAppearanceByStatus } from './constants';
15
+ import { ButtonIcon } from './helperComponents/ButtonIcon';
16
+ import { ButtonTextNeutral, ButtonTextNeutralProps } from './helperComponents/ButtonTextNeutral';
17
+ import { FileItem } from './helperComponents/FileItem';
18
+ import { LoadingStatus } from './helperComponents/LoadingStatus';
19
+ import styles from './styles.module.scss';
20
+ import { ToastUploadStatus, UploadActions, UploadItem } from './types';
21
+ import { formatPercent } from './utils';
22
+
23
+ export type ToastUploadProps = Partial<RtToastContentProps> &
24
+ WithSupportProps<{
25
+ /**
26
+ * Общий статус загрузки
27
+ */
28
+ status: ToastUploadStatus;
29
+ /**
30
+ * Заголовок тостера
31
+ */
32
+ title?: string;
33
+ /**
34
+ * Описание статуса загрузки
35
+ */
36
+ description: string;
37
+ /**
38
+ * Общий прогресс загрузки
39
+ */
40
+ progress: {
41
+ current: number;
42
+ total: number;
43
+ };
44
+ /**
45
+ * Закрыть тостер
46
+ */
47
+ onCloseClick?(e: MouseEvent<HTMLButtonElement>, close?: () => void): void;
48
+ /**
49
+ * Показывать кнопку закрытия тостера
50
+ */
51
+ closable?: boolean;
52
+ /**
53
+ * CSS-класс контейнера
54
+ */
55
+ className?: string;
56
+ /**
57
+ * Загружаемые элементы
58
+ */
59
+ files: UploadItem[];
60
+ /**
61
+ * Тостер свернут/развернут
62
+ */
63
+ collapsed?: boolean;
64
+ /**
65
+ * Развернуть/свернуть тостер
66
+ */
67
+ onCollapsed?: (collapsed: boolean) => void;
68
+ /**
69
+ * Экшены для управления загрузкой
70
+ */
71
+ generalActions: Omit<UploadActions, 'onCancel'>;
72
+ /**
73
+ * Отмена всей загрузки
74
+ */
75
+ cancelButton?: ButtonTextNeutralProps;
76
+ }>;
77
+
78
+ export function ToastUpload({
79
+ status,
80
+ title,
81
+ description,
82
+ closeToast,
83
+ onCloseClick,
84
+ closable,
85
+ className,
86
+ files,
87
+ collapsed,
88
+ onCollapsed,
89
+ generalActions,
90
+ cancelButton,
91
+ progress,
92
+ ...rest
93
+ }: ToastUploadProps) {
94
+ const [isCollapsed, setIsCollapsed] = useUncontrolledProp(collapsed, false, onCollapsed);
95
+
96
+ const { t } = useLocale('ToastUpload');
97
+
98
+ const showingTitle = title || t(`title.${status}`);
99
+
100
+ const handleCloseClick = (e: MouseEvent<HTMLButtonElement>) => {
101
+ e.stopPropagation();
102
+ onCloseClick ? onCloseClick(e, closeToast) : closeToast?.();
103
+ };
104
+
105
+ const handleCollapseClick = (e: MouseEvent<HTMLButtonElement>) => {
106
+ e.stopPropagation();
107
+ setIsCollapsed(!isCollapsed);
108
+ };
109
+
110
+ const progressPercent = Math.round((progress.current / progress.total) * 100);
111
+ const isErrorUploaded = status === 'errorUploaded';
112
+
113
+ return (
114
+ <div
115
+ className={cn(styles.container, className)}
116
+ {...extractSupportProps(rest)}
117
+ data-collapsed={isCollapsed || undefined}
118
+ >
119
+ <div className={styles.titleLine}>
120
+ <div className={styles.titleLineBody}>
121
+ <div className={styles.title} data-test-id={TOAST_UPLOAD_TEST_IDS.title}>
122
+ {showingTitle}
123
+ </div>
124
+
125
+ {cancelButton && <ButtonTextNeutral {...cancelButton} />}
126
+
127
+ <ButtonIcon onClick={handleCollapseClick} data-test-id={TOAST_UPLOAD_TEST_IDS.collapseButton}>
128
+ {!isCollapsed ? <ChevronUpSVG /> : <ChevronDownSVG />}
129
+ </ButtonIcon>
130
+
131
+ {closable && (
132
+ <ButtonIcon
133
+ className={styles.buttonAction}
134
+ onClick={handleCloseClick}
135
+ data-test-id={TOAST_UPLOAD_TEST_IDS.close}
136
+ >
137
+ <CrossSVG />
138
+ </ButtonIcon>
139
+ )}
140
+ </div>
141
+
142
+ <div className={styles.generalProgress}>
143
+ <div className={styles.statusLine}>
144
+ <div className={styles.statusWrap}>
145
+ <LoadingStatus status={status} actions={generalActions} />
146
+
147
+ <TruncateString
148
+ className={styles.description}
149
+ data-status={status}
150
+ text={description}
151
+ data-test-id={TOAST_UPLOAD_TEST_IDS.description}
152
+ />
153
+ </div>
154
+
155
+ <span className={styles.totalCounter} data-test-id={TOAST_UPLOAD_TEST_IDS.counter}>
156
+ {`${progress.current}/${progress.total}`}
157
+ </span>
158
+
159
+ <span className={styles.totalPercentage} data-test-id={TOAST_UPLOAD_TEST_IDS.progress}>
160
+ {formatPercent(isErrorUploaded ? 0 : progressPercent)}
161
+ </span>
162
+ </div>
163
+
164
+ {isCollapsed && (
165
+ <ProgressBar
166
+ progress={isErrorUploaded ? 100 : progressPercent}
167
+ size='xs'
168
+ appearance={progressBarAppearanceByStatus[status]}
169
+ data-test-id={TOAST_UPLOAD_TEST_IDS.progressBar}
170
+ />
171
+ )}
172
+ </div>
173
+ </div>
174
+
175
+ <Scroll className={styles.list} size='s' data-test-id={TOAST_UPLOAD_TEST_IDS.list} barHideStrategy='never'>
176
+ {files.map(item => (
177
+ <FileItem key={item.id || item.title} item={item} />
178
+ ))}
179
+ </Scroll>
180
+ </div>
181
+ );
182
+ }
@@ -0,0 +1,26 @@
1
+ import { ProgressBarProps } from '@snack-uikit/progress-bar';
2
+
3
+ import { ToastUploadStatus } from './types';
4
+
5
+ export const TOAST_UPLOAD_STATUS = {
6
+ Loading: 'loading',
7
+ Pause: 'pause',
8
+ Error: 'error',
9
+ Uploaded: 'uploaded',
10
+ ErrorUploaded: 'errorUploaded',
11
+ } as const;
12
+
13
+ export const TOAST_UPLOAD_ITEM_STATUS = {
14
+ Loading: 'loading',
15
+ Pause: 'pause',
16
+ Error: 'error',
17
+ Uploaded: 'uploaded',
18
+ } as const;
19
+
20
+ export const progressBarAppearanceByStatus: Record<ToastUploadStatus, ProgressBarProps['appearance']> = {
21
+ pause: 'neutral',
22
+ loading: 'green',
23
+ uploaded: 'green',
24
+ errorUploaded: 'red',
25
+ error: 'red',
26
+ };
@@ -0,0 +1,10 @@
1
+ import cn from 'classnames';
2
+ import { ButtonHTMLAttributes } from 'react';
3
+
4
+ import styles from './styles.module.scss';
5
+
6
+ export type ButtonIconProps = ButtonHTMLAttributes<HTMLButtonElement>;
7
+
8
+ export function ButtonIcon({ className, ...props }: ButtonIconProps) {
9
+ return <button type='button' className={cn(styles.buttonIcon, className)} {...props}></button>;
10
+ }
@@ -0,0 +1 @@
1
+ export * from './ButtonIcon';
@@ -0,0 +1,43 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-toaster-toasterSystemEvent';
2
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
3
+ @use '@snack-uikit/figma-tokens/build/scss/styles-theme-variables';
4
+
5
+ .buttonIcon {
6
+ @include styles-tokens-toaster-toasterSystemEvent.composite-var(styles-tokens-toaster-toasterSystemEvent.$toaster-system-event-toast-button-button-neutral);
7
+
8
+ cursor: pointer;
9
+
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+
14
+ box-sizing: border-box;
15
+ margin: 0;
16
+ padding: 0;
17
+
18
+ color: styles-theme-variables.$sys-invert-neutral-text-light;
19
+
20
+ background-color: transparent;
21
+ border: none;
22
+
23
+ svg {
24
+ width: styles-tokens-element.$icon-s !important; /* stylelint-disable-line declaration-no-important */
25
+ height: styles-tokens-element.$icon-s !important; /* stylelint-disable-line declaration-no-important */
26
+ }
27
+
28
+ &:hover {
29
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
30
+ }
31
+
32
+ &:active {
33
+ color: styles-theme-variables.$sys-invert-neutral-accent-pressed;
34
+ }
35
+
36
+ &:focus-visible {
37
+ @include styles-theme-variables.outline-var(styles-tokens-element.$container-focused-s);
38
+
39
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
40
+ outline-color: styles-theme-variables.$sys-available-on-complementary;
41
+ outline-offset: styles-theme-variables.$spacing-state-focus-offset;
42
+ }
43
+ }
@@ -0,0 +1,23 @@
1
+ import { MouseEvent } from 'react';
2
+
3
+ import styles from './styles.module.scss';
4
+
5
+ export type ButtonTextNeutralProps = {
6
+ label: string;
7
+ onClick(e: MouseEvent<HTMLButtonElement>): void;
8
+ critical?: boolean;
9
+ };
10
+
11
+ export function ButtonTextNeutral({ label, onClick, critical }: ButtonTextNeutralProps) {
12
+ return (
13
+ <button
14
+ type='button'
15
+ className={styles.buttonTextNeutral}
16
+ onClick={onClick}
17
+ aria-label='action'
18
+ data-critical={critical || undefined}
19
+ >
20
+ {label}
21
+ </button>
22
+ );
23
+ }
@@ -0,0 +1 @@
1
+ export * from './ButtonTextNeutral';
@@ -0,0 +1,38 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-toaster-toasterSystemEvent';
2
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
3
+ @use '@snack-uikit/figma-tokens/build/scss/styles-theme-variables';
4
+
5
+ .buttonTextNeutral {
6
+ @include styles-tokens-toaster-toasterSystemEvent.composite-var(styles-tokens-toaster-toasterSystemEvent.$toaster-system-event-toast-button-button-neutral);
7
+ @include styles-theme-variables.composite-var(styles-theme-variables.$sans-body-m);
8
+
9
+ cursor: pointer;
10
+
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+
15
+ box-sizing: border-box;
16
+ margin: 0;
17
+ padding: 0;
18
+
19
+ color: styles-theme-variables.$sys-invert-neutral-text-light;
20
+
21
+ background-color: transparent;
22
+ border: none;
23
+
24
+ &:hover {
25
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
26
+ }
27
+
28
+ &:active {
29
+ color: styles-theme-variables.$sys-invert-neutral-accent-pressed;
30
+ }
31
+
32
+ &:focus-visible {
33
+ @include styles-theme-variables.outline-var(styles-tokens-element.$container-focused-s);
34
+
35
+ outline-color: styles-theme-variables.$sys-available-on-complementary;
36
+ outline-offset: styles-theme-variables.$spacing-state-focus-offset;
37
+ }
38
+ }
@@ -0,0 +1,83 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import { CrossSVG } from '@snack-uikit/icons';
4
+ import { Link } from '@snack-uikit/link';
5
+ import { ProgressBar } from '@snack-uikit/progress-bar';
6
+ import { TruncateString } from '@snack-uikit/truncate-string';
7
+
8
+ import { TOAST_UPLOAD_TEST_IDS } from '../../../../testIds';
9
+ import { progressBarAppearanceByStatus } from '../../constants';
10
+ import { UploadItem } from '../../types';
11
+ import { formatPercent } from '../../utils';
12
+ import { ButtonIcon } from '../ButtonIcon';
13
+ import { LoadingStatus } from '../LoadingStatus';
14
+ import styles from './styles.module.scss';
15
+
16
+ export type FileItemProps = { item: UploadItem };
17
+
18
+ export function FileItem({ item: initItem }: FileItemProps) {
19
+ const [item, setItem] = useState(initItem);
20
+
21
+ const showLink = item.status === 'uploaded' && item.link;
22
+
23
+ const showCancelButton = !showLink;
24
+
25
+ useEffect(() => {
26
+ if (initItem.subscribeToState) {
27
+ initItem.subscribeToState(newValue => setItem(prev => ({ ...prev, ...newValue })));
28
+ }
29
+
30
+ return setItem(initItem);
31
+ }, [initItem]);
32
+
33
+ const isError = item.status === 'error';
34
+
35
+ return (
36
+ <div className={styles.fileLine} data-test-id={TOAST_UPLOAD_TEST_IDS.uploadItem}>
37
+ <div className={styles.fileHeadLine}>
38
+ <TruncateString text={item.title} className={styles.fileTitle} maxLines={1} />
39
+
40
+ {showLink && (
41
+ <Link
42
+ text={item.link?.text}
43
+ href={item.link?.href}
44
+ size='s'
45
+ truncateVariant='end'
46
+ onClick={item.link?.onClick}
47
+ appearance='invert-neutral'
48
+ textMode='accent'
49
+ data-test-id={TOAST_UPLOAD_TEST_IDS.uploadItemLink}
50
+ />
51
+ )}
52
+
53
+ {showCancelButton && (
54
+ <ButtonIcon onClick={item.actions.onCancel} data-test-id={TOAST_UPLOAD_TEST_IDS.uploadItemCancel}>
55
+ <CrossSVG />
56
+ </ButtonIcon>
57
+ )}
58
+ </div>
59
+
60
+ <ProgressBar
61
+ progress={isError ? 100 : item.progress}
62
+ size='xs'
63
+ appearance={progressBarAppearanceByStatus[item.status]}
64
+ />
65
+
66
+ <div className={styles.fileStatusLine}>
67
+ <div className={styles.fileStatusWrap}>
68
+ <LoadingStatus status={item.status} actions={item.actions} isFileItem />
69
+
70
+ <TruncateString className={styles.fileStatusDescription} data-status={item.status} text={item.statusLabel} />
71
+ </div>
72
+
73
+ <span className={styles.fileSize} data-status={item.status}>
74
+ {item.formattedSize}
75
+ </span>
76
+
77
+ <span className={styles.fileStatusPercentage} data-status={item.status}>
78
+ {formatPercent(isError ? 0 : item.progress)}
79
+ </span>
80
+ </div>
81
+ </div>
82
+ );
83
+ }
@@ -0,0 +1 @@
1
+ export * from './FileItem';
@@ -0,0 +1,91 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-toaster-toasterUpload';
2
+ @use '@snack-uikit/figma-tokens/build/scss/styles-theme-variables';
3
+
4
+ .fileLine {
5
+ @include styles-theme-variables.composite-var(
6
+ styles-tokens-toaster-toasterUpload.$toaster-upload-file-line-container
7
+ );
8
+
9
+ display: flex;
10
+ flex-direction: column;
11
+ }
12
+
13
+ .fileHeadLine {
14
+ @include styles-theme-variables.composite-var(
15
+ styles-tokens-toaster-toasterUpload.$toaster-upload-file-line-headline
16
+ );
17
+
18
+ display: flex;
19
+ align-items: center;
20
+ }
21
+
22
+ .fileTitle {
23
+ @include styles-theme-variables.composite-var(styles-theme-variables.$sans-body-s);
24
+
25
+ flex-grow: 1;
26
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
27
+ }
28
+
29
+
30
+ .fileStatusLine {
31
+ @include styles-theme-variables.composite-var(
32
+ styles-tokens-toaster-toasterUpload.$toaster-upload-status-line-container
33
+ );
34
+
35
+ display: flex;
36
+ align-items: center;
37
+ }
38
+
39
+ .fileStatusWrap {
40
+ @include styles-theme-variables.composite-var(
41
+ styles-tokens-toaster-toasterUpload.$toaster-upload-status-line-status-wrap
42
+ );
43
+
44
+ overflow: hidden;
45
+ display: flex;
46
+ flex-grow: 1;
47
+ align-items: center;
48
+ }
49
+
50
+ .fileStatusDescription {
51
+ @include styles-theme-variables.composite-var(styles-theme-variables.$sans-body-s);
52
+
53
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
54
+
55
+ &[data-status='loading'] {
56
+ color: styles-theme-variables.$sys-invert-neutral-text-disabled;
57
+ }
58
+
59
+ &[data-status='uploaded'] {
60
+ color: styles-theme-variables.$sys-invert-neutral-text-disabled;
61
+ }
62
+ }
63
+
64
+ .fileSize {
65
+ @include styles-theme-variables.composite-var(styles-theme-variables.$sans-body-s);
66
+
67
+ color: styles-theme-variables.$sys-invert-neutral-text-disabled;
68
+ text-wrap: nowrap;
69
+
70
+ &[data-status='loading'] {
71
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
72
+ }
73
+
74
+ &[data-status='uploaded'] {
75
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
76
+ }
77
+ }
78
+
79
+ .fileStatusPercentage {
80
+ @include styles-theme-variables.composite-var(styles-theme-variables.$sans-body-s);
81
+
82
+ color: styles-theme-variables.$sys-invert-neutral-text-disabled;
83
+
84
+ &[data-status='loading'] {
85
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
86
+ }
87
+
88
+ &[data-status='uploaded'] {
89
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
90
+ }
91
+ }
@@ -0,0 +1,39 @@
1
+ import { CheckFilledSVG, PauseSVG, PlaySVG, UpdateSVG } from '@snack-uikit/icons';
2
+
3
+ import { ToastUploadStatus, UploadActions } from '../../types';
4
+ import { ButtonIcon } from '../ButtonIcon';
5
+ import styles from './styles.module.scss';
6
+
7
+ export type LoadingStatusProps = {
8
+ status: ToastUploadStatus;
9
+ actions: UploadActions;
10
+ isFileItem?: boolean;
11
+ };
12
+
13
+ export function LoadingStatus({ status, actions, isFileItem }: LoadingStatusProps) {
14
+ if (status === 'loading' || (status === 'error' && !isFileItem)) {
15
+ return (
16
+ <ButtonIcon onClick={actions.onPause}>
17
+ <PauseSVG />
18
+ </ButtonIcon>
19
+ );
20
+ }
21
+
22
+ if (status === 'pause') {
23
+ return (
24
+ <ButtonIcon onClick={actions.onContinue}>
25
+ <PlaySVG />
26
+ </ButtonIcon>
27
+ );
28
+ }
29
+
30
+ if (status === 'uploaded') {
31
+ return <CheckFilledSVG className={styles.successIcon} />;
32
+ }
33
+
34
+ return (
35
+ <ButtonIcon onClick={actions.onRetry}>
36
+ <UpdateSVG />
37
+ </ButtonIcon>
38
+ );
39
+ }
@@ -0,0 +1 @@
1
+ export * from './LoadingStatus';
@@ -0,0 +1,5 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/styles-theme-variables';
2
+
3
+ .successIcon {
4
+ color: styles-theme-variables.$sys-green-accent-default;
5
+ }
@@ -0,0 +1 @@
1
+ export * from './ToastUpload';
@@ -0,0 +1,121 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-toaster-toasterSystemEvent';
2
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-toaster-toasterUpload';
3
+ @use '@snack-uikit/figma-tokens/build/scss/styles-theme-variables';
4
+
5
+ .container {
6
+ @include styles-theme-variables.composite-var(styles-tokens-toaster-toasterUpload.$toaster-upload-toast-container);
7
+
8
+ cursor: default;
9
+
10
+ position: relative;
11
+
12
+ overflow: hidden;
13
+ display: flex;
14
+ flex-direction: column;
15
+
16
+ box-sizing: border-box;
17
+ width: calc(
18
+ styles-tokens-toaster-toasterSystemEvent.$size-toaster-toast-system-event-container - styles-tokens-toaster-toasterUpload.$space-toaster-toast-system-event-container-padding
19
+ );
20
+ max-width: 100%;
21
+ max-height: 386px;
22
+
23
+ background-color: styles-theme-variables.$sys-invert-neutral-background;
24
+
25
+ &[data-collapsed] {
26
+ gap: 0;
27
+ }
28
+ }
29
+
30
+ .titleLine {
31
+ display: flex;
32
+ flex-direction: column;
33
+ }
34
+
35
+ .titleLineBody {
36
+ @include styles-theme-variables.composite-var(styles-tokens-toaster-toasterUpload.$toaster-upload-title-line-body);
37
+
38
+ display: flex;
39
+ align-items: center;
40
+ }
41
+
42
+ .title {
43
+ @include styles-theme-variables.composite-var(styles-theme-variables.$sans-title-s);
44
+
45
+ flex-grow: 1;
46
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
47
+ }
48
+
49
+ .generalProgress {
50
+ @include styles-theme-variables.composite-var(
51
+ styles-tokens-toaster-toasterUpload.$toaster-upload-title-line-general-progress
52
+ );
53
+
54
+ display: flex;
55
+ flex-direction: column;
56
+ align-self: stretch;
57
+ }
58
+
59
+ .statusLine {
60
+ @include styles-theme-variables.composite-var(
61
+ styles-tokens-toaster-toasterUpload.$toaster-upload-status-line-container
62
+ );
63
+
64
+ display: flex;
65
+ align-items: center;
66
+ }
67
+
68
+ .statusWrap {
69
+ @include styles-theme-variables.composite-var(
70
+ styles-tokens-toaster-toasterUpload.$toaster-upload-status-line-status-wrap
71
+ );
72
+
73
+ overflow: hidden;
74
+ display: flex;
75
+ flex-grow: 1;
76
+ align-items: center;
77
+ }
78
+
79
+ .description {
80
+ @include styles-theme-variables.composite-var(styles-theme-variables.$sans-body-m);
81
+
82
+ color: styles-theme-variables.$sys-invert-neutral-text-main;
83
+
84
+ &[data-status='loading'] {
85
+ color: styles-theme-variables.$sys-invert-neutral-text-light;
86
+ }
87
+
88
+ &[data-status='uploaded'] {
89
+ color: styles-theme-variables.$sys-invert-neutral-text-light;
90
+ }
91
+ }
92
+
93
+ .totalCounter {
94
+ @include styles-theme-variables.composite-var(styles-theme-variables.$sans-body-m);
95
+
96
+ color: styles-theme-variables.$sys-invert-neutral-text-support;
97
+ }
98
+
99
+ .totalPercentage {
100
+ @include styles-theme-variables.composite-var(styles-theme-variables.$sans-body-m);
101
+
102
+ color: styles-theme-variables.$sys-invert-neutral-text-support;
103
+ }
104
+
105
+ .list {
106
+ overflow-x: hidden;
107
+ display: flex;
108
+ flex-direction: column;
109
+
110
+ box-sizing: content-box;
111
+ max-height: 288px;
112
+ margin-top: 0;
113
+ // TODO: use tokens
114
+ padding-right: calc(#{styles-theme-variables.$space-toaster-toast-system-event-container-padding} - #{styles-theme-variables.$dimension-050m});
115
+
116
+ transition: all 0.1s;
117
+
118
+ [data-collapsed] & {
119
+ max-height: 0;
120
+ }
121
+ }