@fibery/ui-kit 1.19.0 → 1.20.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 (50) hide show
  1. package/package.json +7 -6
  2. package/src/a11y-color.ts +9 -6
  3. package/src/antd/styles.ts +6 -6
  4. package/src/button/button.tsx +5 -5
  5. package/src/design-system.ts +19 -18
  6. package/src/emoji-picker/emoji-picker-content-with-color.tsx +1 -1
  7. package/src/emoji-picker/emoji-picker.tsx +2 -2
  8. package/src/emoji-picker/icon-emoji-picker.tsx +1 -1
  9. package/src/emoji-picker/primitives/category.tsx +2 -2
  10. package/src/emoji-picker/primitives/emoji.tsx +26 -3
  11. package/src/emoji-picker/primitives/footer.tsx +1 -1
  12. package/src/emoji-picker/primitives/header.tsx +1 -1
  13. package/src/emoji-picker/primitives/layout.ts +2 -2
  14. package/src/emoji-picker/primitives/search.tsx +5 -5
  15. package/src/error-alert.tsx +3 -3
  16. package/src/form-field-loader.tsx +3 -3
  17. package/src/icons/ast/AlertTriangle.ts +8 -0
  18. package/src/icons/ast/DragBlockHandleSingle.ts +8 -0
  19. package/src/icons/ast/Eye.ts +8 -0
  20. package/src/icons/ast/NetworkAdd.ts +8 -0
  21. package/src/icons/ast/RemovePeople.ts +8 -0
  22. package/src/icons/ast/RicheditorMarkTextBackgroundColor.ts +8 -0
  23. package/src/icons/ast/index.tsx +6 -1
  24. package/src/icons/react/AlertTriangle.tsx +12 -0
  25. package/src/icons/react/DragBlockHandleSingle.tsx +12 -0
  26. package/src/icons/react/Eye.tsx +12 -0
  27. package/src/icons/react/NetworkAdd.tsx +12 -0
  28. package/src/icons/react/RemovePeople.tsx +12 -0
  29. package/src/icons/react/RicheditorMarkTextBackgroundColor.tsx +12 -0
  30. package/src/icons/react/index.tsx +6 -1
  31. package/src/loaders.tsx +10 -10
  32. package/src/loading-sausage.tsx +2 -2
  33. package/src/media-query-utils.ts +1 -1
  34. package/src/select/custom-select-partials/group-heading.tsx +2 -2
  35. package/src/select/custom-select-partials/menu.tsx +9 -1
  36. package/src/select/custom-select-partials/no-option-message.tsx +1 -1
  37. package/src/select/custom-select-partials/option.tsx +64 -7
  38. package/src/select/index.tsx +1 -1
  39. package/src/select/select-in-popover.tsx +10 -10
  40. package/src/select/styles.ts +12 -4
  41. package/src/toast/primitives.tsx +21 -8
  42. package/src/toast/toast-action.tsx +9 -1
  43. package/src/toast/toast-provider.tsx +15 -0
  44. package/src/toast/{toast-queue.tsx → toast-queue.ts} +21 -25
  45. package/src/toast/toast.tsx +35 -28
  46. package/src/toast/toaster.tsx +11 -48
  47. package/src/toast/utils/toastify-item-name.ts +6 -0
  48. package/src/tooltip.tsx +2 -2
  49. package/src/icons/ast/RicheditorMarkHighlight.ts +0 -8
  50. package/src/icons/react/RicheditorMarkHighlight.tsx +0 -12
@@ -0,0 +1,12 @@
1
+ // This icon file is generated automatically.
2
+
3
+ import RicheditorMarkTextBackgroundColorSvg from '../ast/RicheditorMarkTextBackgroundColor';
4
+ import { Icon } from '../Icon';
5
+ import { IconBaseProps } from '../types';
6
+
7
+ const RicheditorMarkTextBackgroundColor = (
8
+ props: IconBaseProps,
9
+ ): JSX.Element => <Icon {...props} icon={RicheditorMarkTextBackgroundColorSvg} />;
10
+
11
+ RicheditorMarkTextBackgroundColor.displayName = 'RicheditorMarkTextBackgroundColor';
12
+ export default RicheditorMarkTextBackgroundColor;
@@ -9,6 +9,7 @@ export { default as AddReactions } from './AddReactions';
9
9
  export { default as Add } from './Add';
10
10
  export { default as AiAssistant } from './AiAssistant';
11
11
  export { default as AiAvatar } from './AiAvatar';
12
+ export { default as AlertTriangle } from './AlertTriangle';
12
13
  export { default as AntdFilledCheckCircle } from './AntdFilledCheckCircle';
13
14
  export { default as AntdFilledCloseCircle } from './AntdFilledCloseCircle';
14
15
  export { default as AntdFilledExclamationCircle } from './AntdFilledExclamationCircle';
@@ -58,6 +59,7 @@ export { default as Delete } from './Delete';
58
59
  export { default as Demo } from './Demo';
59
60
  export { default as DoubleArrowLeft } from './DoubleArrowLeft';
60
61
  export { default as DoubleArrowRight } from './DoubleArrowRight';
62
+ export { default as DragBlockHandleSingle } from './DragBlockHandleSingle';
61
63
  export { default as DragBlockHandle } from './DragBlockHandle';
62
64
  export { default as DragHandle } from './DragHandle';
63
65
  export { default as DynamicFilterValue } from './DynamicFilterValue';
@@ -70,6 +72,7 @@ export { default as ExtensionComments } from './ExtensionComments';
70
72
  export { default as ExtensionFiles } from './ExtensionFiles';
71
73
  export { default as ExtensionReferences } from './ExtensionReferences';
72
74
  export { default as ExtensionWorkflow } from './ExtensionWorkflow';
75
+ export { default as Eye } from './Eye';
73
76
  export { default as FavoritesChecked } from './FavoritesChecked';
74
77
  export { default as FavoritesOff } from './FavoritesOff';
75
78
  export { default as Favorites } from './Favorites';
@@ -114,6 +117,7 @@ export { default as Monitor } from './Monitor';
114
117
  export { default as MoreCompact } from './MoreCompact';
115
118
  export { default as More } from './More';
116
119
  export { default as MySpace } from './MySpace';
120
+ export { default as NetworkAdd } from './NetworkAdd';
117
121
  export { default as Network } from './Network';
118
122
  export { default as Notifications } from './Notifications';
119
123
  export { default as ObjChild } from './ObjChild';
@@ -130,6 +134,7 @@ export { default as People } from './People';
130
134
  export { default as Photo } from './Photo';
131
135
  export { default as Popup } from './Popup';
132
136
  export { default as Posts } from './Posts';
137
+ export { default as RemovePeople } from './RemovePeople';
133
138
  export { default as Remove } from './Remove';
134
139
  export { default as Reply } from './Reply';
135
140
  export { default as Restore } from './Restore';
@@ -167,9 +172,9 @@ export { default as RicheditorListOrdered } from './RicheditorListOrdered';
167
172
  export { default as RicheditorListUnindent } from './RicheditorListUnindent';
168
173
  export { default as RicheditorMarkCode } from './RicheditorMarkCode';
169
174
  export { default as RicheditorMarkEm } from './RicheditorMarkEm';
170
- export { default as RicheditorMarkHighlight } from './RicheditorMarkHighlight';
171
175
  export { default as RicheditorMarkStrike } from './RicheditorMarkStrike';
172
176
  export { default as RicheditorMarkStrong } from './RicheditorMarkStrong';
177
+ export { default as RicheditorMarkTextBackgroundColor } from './RicheditorMarkTextBackgroundColor';
173
178
  export { default as RicheditorMarkUnderline } from './RicheditorMarkUnderline';
174
179
  export { default as RicheditorOpenLink } from './RicheditorOpenLink';
175
180
  export { default as RicheditorTableCreate } from './RicheditorTableCreate';
package/src/loaders.tsx CHANGED
@@ -16,7 +16,7 @@ export const LoadingView = styled.div<{absolute: boolean; backgroundColor?: stri
16
16
  left: 0,
17
17
  bottom: 0,
18
18
  right: 0,
19
- padding: space.xl,
19
+ padding: space.s24,
20
20
  }}
21
21
  `;
22
22
 
@@ -28,8 +28,8 @@ export const LoadingHeader = styled.div<{absolute: boolean}>`
28
28
  right: 0,
29
29
  height: layout.viewHeaderHeight,
30
30
  backgroundColor: themeVars.mainBg,
31
- paddingBottom: space.l,
32
- paddingLeft: space.l + space.s,
31
+ paddingBottom: space.s12,
32
+ paddingLeft: space.s12 + space.s6,
33
33
  display: "flex",
34
34
  justifyContent: "flex-end",
35
35
  flexDirection: "column",
@@ -52,7 +52,7 @@ export const SidebarLoadingPage = () => (
52
52
  ${{
53
53
  width: layout.typeEditorWidth,
54
54
  minWidth: layout.typeEditorMinWidth,
55
- paddingBottom: space.xl,
55
+ paddingBottom: space.s24,
56
56
  overflowY: "scroll",
57
57
  height: "100%",
58
58
  }}
@@ -64,20 +64,20 @@ export const SidebarLoadingPage = () => (
64
64
 
65
65
  const loaderWrapperStyle = css`
66
66
  ${{
67
- padding: space.xxl,
67
+ padding: space.s48,
68
68
  height: "100%",
69
69
  display: "flex",
70
70
  alignItems: "center",
71
71
  justifyContent: "center",
72
72
  flexDirection: "column",
73
- marginLeft: space.l,
74
- marginRight: space.l,
73
+ marginLeft: space.s12,
74
+ marginRight: space.s12,
75
75
  borderRadius: border.radius6,
76
76
  }}
77
77
  `;
78
78
 
79
79
  const loaderStyle = css`
80
- ${{paddingBottom: space.s, ...textStyles.regular}}
80
+ ${{paddingBottom: space.s6, ...textStyles.regular}}
81
81
  `;
82
82
  export const LoadingApp = ({backgroundColor = "transparent", appName}: {backgroundColor?: string; appName: string}) => (
83
83
  <div className={loaderWrapperStyle} style={{backgroundColor}}>
@@ -158,12 +158,12 @@ export const LoadingTreeMenuItem = ({
158
158
  level === 0 &&
159
159
  css`
160
160
  padding-left: 5px;
161
- gap: ${space.s}px;
161
+ gap: ${space.s6}px;
162
162
  `
163
163
  )}
164
164
  >
165
165
  <Icon color={color} containerSize={layout.newMenuItemHeight} iconSize={18} />
166
- {children || <LoadingSausage height={space.s} inverted={theme.mode === "dark"} />}
166
+ {children || <LoadingSausage height={space.s6} inverted={theme.mode === "dark"} />}
167
167
  </div>
168
168
  );
169
169
  };
@@ -79,8 +79,8 @@ const dark = css`
79
79
 
80
80
  const marginStyle = css`
81
81
  ${{
82
- marginTop: space.xs,
83
- marginBottom: space.xs,
82
+ marginTop: space.s4,
83
+ marginBottom: space.s4,
84
84
  }}
85
85
  `;
86
86
 
@@ -2,7 +2,7 @@
2
2
  const noop = () => {};
3
3
  export function getMediaQueryList(query: string) {
4
4
  // need for test
5
- return typeof window !== undefined && window.matchMedia
5
+ return typeof window !== "undefined" && window.matchMedia
6
6
  ? window.matchMedia(query)
7
7
  : ({matches: false, addEventListener: noop, removeEventListener: noop} as unknown as MediaQueryList);
8
8
  }
@@ -17,11 +17,11 @@ const groupHeadingClass = css`
17
17
  white-space: nowrap;
18
18
  overflow: hidden;
19
19
  text-overflow: ellipsis;
20
- padding: ${space.m + space.xs}px ${space.m}px ${space.l}px ${space.m}px;
20
+ padding: ${space.s8 + space.s4}px ${space.s8}px ${space.s12}px ${space.s8}px;
21
21
  color: ${themeVars.disabledTextColor};
22
22
  `;
23
23
  const groupDividerContainerStyle = css`
24
- padding: ${space.l}px 0;
24
+ padding: ${space.s12}px 0;
25
25
  box-sizing: content-box;
26
26
  `;
27
27
 
@@ -3,12 +3,18 @@ import {CSSObjectWithLabel, MenuProps} from "react-select";
3
3
  import cn from "classnames";
4
4
  import {components} from "react-windowed-select";
5
5
  import {css} from "@linaria/core";
6
- import {border, themeVars} from "../../design-system";
6
+ import {border, space, themeVars} from "../../design-system";
7
7
 
8
+ export const MenuListNulledStyles: CSSObjectWithLabel = {
9
+ paddingTop: "",
10
+ paddingBottom: "",
11
+ };
8
12
  export const MenuNulledStyles: CSSObjectWithLabel = {
9
13
  backgroundColor: "",
10
14
  borderRadius: "",
11
15
  boxShadow: "",
16
+ paddingTop: "",
17
+ paddingBottom: "",
12
18
  };
13
19
  const MenuRootClass = css`
14
20
  color: ${themeVars.disabledTextColor};
@@ -16,6 +22,8 @@ const MenuRootClass = css`
16
22
  background-color: ${themeVars.colorBgSelectMenu};
17
23
  box-shadow: ${themeVars.shadowSelectMenu};
18
24
  border-radius: ${border.radius6}px;
25
+ padding-top: ${space.s6}px;
26
+ padding-bottom: ${space.s6}px;
19
27
  overflow: hidden;
20
28
  `;
21
29
  export function Menu<Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>>(
@@ -3,7 +3,7 @@ import {css} from "@linaria/core";
3
3
  import {space} from "../../design-system";
4
4
 
5
5
  const NopOptionMessageClass = css`
6
- padding: ${space.l + 2}px ${space.m}px;
6
+ padding: ${space.s12 + 2}px ${space.s8}px;
7
7
  `;
8
8
  export const NoOptionsMessage = ({children}: {children: ReactNode}) => {
9
9
  return <div className={NopOptionMessageClass}>{children}</div>;
@@ -3,14 +3,16 @@ import {components} from "react-windowed-select";
3
3
  import {GroupBase} from "../index";
4
4
  import {css} from "@linaria/core";
5
5
  import {layout, space} from "../../design-system";
6
- import {ListItemGeneral} from "../../lists/list-item-general";
6
+ import {ListItem} from "../../lists/list-item";
7
7
  import cn from "classnames";
8
+ import {Children, ReactNode, useCallback, useMemo, useRef, useState} from "react";
9
+ import {Tooltip} from "antd";
8
10
 
9
11
  export const OptionNulledVitualizedStyles = {
10
- height: layout.newMenuItemHeight,
12
+ height: layout.menuItemHeight,
11
13
  };
12
14
  export const OptionNulledStyles = {
13
- minHeight: layout.newMenuItemHeight,
15
+ minHeight: layout.menuItemHeight,
14
16
  paddingLeft: "",
15
17
  paddingRight: "",
16
18
  paddingTop: "",
@@ -21,13 +23,63 @@ export const OptionNulledStyles = {
21
23
  color: "",
22
24
  };
23
25
  const OptionRootClass = css`
24
- padding: 0 ${space.s}px;
26
+ padding: 0 ${space.s6}px;
25
27
  display: flex;
26
28
  align-items: stretch;
27
29
  justify-content: stretch;
28
30
  cursor: pointer;
31
+ max-width: 100%;
29
32
  `;
30
33
 
34
+ const optionWithTextContent = css`
35
+ max-width: 100%;
36
+ padding: 0 ${space.s4}px;
37
+ `;
38
+
39
+ const optionTextOverflowWrapper = css`
40
+ max-width: 100%;
41
+ flex-basis: 100%;
42
+ overflow: hidden;
43
+ text-overflow: ellipsis;
44
+ white-space: nowrap;
45
+ padding: 0 ${space.s4}px;
46
+ `;
47
+
48
+ function isOptionContentIsPureText(children: ReactNode) {
49
+ return !Children.toArray(children).find((child) => !(typeof child === "string" || typeof child === "number"));
50
+ }
51
+
52
+ type OptionTextContentWithTooltipProps = {
53
+ children: string | number;
54
+ isFocused?: boolean;
55
+ };
56
+
57
+ function OptionTextContentWithTooltip({children, isFocused}: OptionTextContentWithTooltipProps) {
58
+ const [isTooltipActive, setTooltipActive] = useState<boolean>(false);
59
+ const textWrapperRef = useRef<HTMLDivElement>(null);
60
+ const textContentRef = useRef<HTMLSpanElement>(null);
61
+ const onOptionMouseEnter = useCallback(() => {
62
+ setTimeout(() => {
63
+ if (!textContentRef.current || !textWrapperRef.current) {
64
+ return;
65
+ }
66
+ isFocused && setTooltipActive(textContentRef.current?.offsetWidth > textWrapperRef.current?.offsetWidth);
67
+ }, 300);
68
+ }, [textContentRef, textWrapperRef, isFocused]);
69
+ return (
70
+ <div
71
+ className={optionWithTextContent}
72
+ onMouseEnter={onOptionMouseEnter}
73
+ onMouseLeave={() => setTooltipActive(false)}
74
+ >
75
+ <Tooltip title={children} mouseEnterDelay={300} placement={"right"} open={isTooltipActive}>
76
+ <div ref={textWrapperRef} className={optionTextOverflowWrapper}>
77
+ <span ref={textContentRef}>{children}</span>
78
+ </div>
79
+ </Tooltip>
80
+ </div>
81
+ );
82
+ }
31
83
  /**
32
84
  * Attention! Do not use this option for potentially long lists inside React Select
33
85
  * Why is this option slow? Read here https://the.fibery.io/SoftDev/bug/Select-option-behaviour-is-incorrect-9413
@@ -38,11 +90,16 @@ export function OptionSlow<
38
90
  Group extends GroupBase<Option> = GroupBase<Option>
39
91
  >({children, ...rest}: OptionProps<Option, IsMulti, Group>) {
40
92
  const {isDisabled, isSelected, isFocused} = rest;
93
+ const isPureText = useMemo(() => isOptionContentIsPureText(children), [children]);
41
94
  return (
42
95
  <components.Option {...rest} className={cn(rest.className, OptionRootClass)}>
43
- <ListItemGeneral focused={isFocused} selected={isSelected} hovered={false} done={isDisabled}>
44
- {children}
45
- </ListItemGeneral>
96
+ <ListItem focused={isFocused} selected={isSelected} hovered={false} done={isDisabled}>
97
+ {isPureText ? (
98
+ <OptionTextContentWithTooltip isFocused={isFocused}>{children as string}</OptionTextContentWithTooltip>
99
+ ) : (
100
+ children
101
+ )}
102
+ </ListItem>
46
103
  </components.Option>
47
104
  );
48
105
  }
@@ -140,7 +140,7 @@ export const Select = forwardRef(function Select<
140
140
  (e) => {
141
141
  switch (e.key) {
142
142
  case "Escape":
143
- // we cannot rely on "menuIsOpen" prop here, as user may not pass this prop and rely on internal component behavior instead.
143
+ // we can't rely on "menuIsOpen" prop here, as user may not pass this prop and rely on internal component behavior instead.
144
144
  if (menuOpenState) {
145
145
  e.stopPropagation();
146
146
  }
@@ -27,14 +27,14 @@ import {
27
27
  } from "./index";
28
28
  import {SelectControlSettingsProvider} from "./select-control-settings-context";
29
29
 
30
- const offset = [0, space.xs] as [number, number];
30
+ const offset = [0, space.s4] as [number, number];
31
31
  const popupClassName = css`
32
32
  z-index: 1050;
33
33
  max-width: 360px;
34
- padding-top: ${space.m}px;
35
- padding-bottom: ${space.xs}px;
36
- padding-left: ${space.m}px;
37
- padding-right: ${space.m}px;
34
+ padding-top: ${space.s8}px;
35
+ padding-bottom: ${space.s4}px;
36
+ padding-left: ${space.s8}px;
37
+ padding-right: ${space.s8}px;
38
38
  `;
39
39
 
40
40
  const valueClassName = css`
@@ -58,10 +58,10 @@ const valueClassName = css`
58
58
 
59
59
  const titleClassName = css`
60
60
  ${textStyles.heading6}
61
- padding-top: ${space.s}px;
62
- padding-left: ${space.m}px;
63
- padding-right: ${space.m}px;
64
- padding-bottom: ${space.l}px;
61
+ padding-top: ${space.s6}px;
62
+ padding-left: ${space.s8}px;
63
+ padding-right: ${space.s8}px;
64
+ padding-bottom: ${space.s12}px;
65
65
  `;
66
66
 
67
67
  export const selectInPopupStyles = {
@@ -78,7 +78,7 @@ export const selectInPopupStyles = {
78
78
  ...props,
79
79
  minWidth: 100,
80
80
  position: "static",
81
- marginTop: space.s,
81
+ marginTop: space.s6,
82
82
  marginBottom: 0,
83
83
  boxShadow: "none",
84
84
  borderRadius: border.radius4,
@@ -3,7 +3,7 @@ import type {ControlProps, CSSObjectWithLabel, StylesConfig} from "react-select"
3
3
  import {inputOverrides} from "../antd/styles";
4
4
  import {layout, space, themeVars} from "../design-system";
5
5
  import {OptionNulledStyles, OptionNulledVitualizedStyles} from "./custom-select-partials/option";
6
- import {MenuNulledStyles} from "./custom-select-partials/menu";
6
+ import {MenuListNulledStyles, MenuNulledStyles} from "./custom-select-partials/menu";
7
7
  import {groupHeadingNulledStyles} from "./custom-select-partials/group-heading";
8
8
 
9
9
  export const expanderExpandedStyle = css`
@@ -35,8 +35,8 @@ function createControlStyle({
35
35
  ...(state.isFocused && inputOverrides.focus),
36
36
  ...(state.isDisabled && inputOverrides.disabled),
37
37
  ":hover": !state.isFocused && !state.isDisabled ? inputOverrides.hover : {},
38
- paddingLeft: space.l,
39
- paddingRight: space.l,
38
+ paddingLeft: space.s10,
39
+ paddingRight: space.s12,
40
40
  "& > div": childDivStyle,
41
41
  } as unknown as CSSObjectWithLabel);
42
42
  }
@@ -78,11 +78,15 @@ export const componentsStyles: StylesConfig = {
78
78
  ...provided,
79
79
  overflow: "visible",
80
80
  color: themeVars.textColor,
81
+ marginLeft: "",
82
+ marginRight: "",
83
+ margin: "",
81
84
  }),
82
85
  valueContainer(provided) {
83
86
  return {
84
87
  ...provided,
85
88
  minWidth: 10,
89
+ gridTemplateColumns: "1fr",
86
90
  };
87
91
  },
88
92
  multiValue: (provided) => ({
@@ -92,7 +96,7 @@ export const componentsStyles: StylesConfig = {
92
96
  marginTop: 0,
93
97
  marginBottom: 0,
94
98
  marginLeft: 0,
95
- marginRight: space.l,
99
+ marginRight: space.s12,
96
100
  alignItems: "center",
97
101
  height: layout.inputHeight - 2,
98
102
  }),
@@ -115,6 +119,10 @@ export const componentsStyles: StylesConfig = {
115
119
  ...provided,
116
120
  ...MenuNulledStyles,
117
121
  }),
122
+ menuList: (provided) => ({
123
+ ...provided,
124
+ ...MenuListNulledStyles,
125
+ }),
118
126
  container: (provided, state) => ({
119
127
  ...provided,
120
128
  pointerEvents: "auto",
@@ -6,8 +6,6 @@ import {IconButton} from "../button/icon-button";
6
6
  import {border, space, textStyles, themeVars} from "../design-system";
7
7
  import CloseIcon from "../icons/react/Close";
8
8
 
9
- export const ToastProvider = ToastPrimitives.Provider;
10
-
11
9
  const viewportCss = css`
12
10
  position: fixed;
13
11
  left: 50%;
@@ -24,7 +22,7 @@ const viewportCss = css`
24
22
  list-style: none;
25
23
 
26
24
  &:not(:empty) {
27
- padding: ${space.xl}px; // increased mouse hover area to pause timers of toast
25
+ padding: ${space.s24}px; // increased mouse hover area to pause timers of toast
28
26
  }
29
27
  `;
30
28
 
@@ -39,11 +37,22 @@ const rootCss = css`
39
37
  user-select: auto !important;
40
38
 
41
39
  background-color: ${themeVars.colorBgToastDefault};
42
- padding: ${space.m}px ${space.m * 2}px;
40
+ padding: ${space.s12}px ${space.s8 * 2}px;
43
41
  border-radius: ${border.radius6}px;
44
42
  box-shadow: ${themeVars.shadowModal};
45
43
 
46
- @keyframes hide {
44
+ will-change: opacity;
45
+
46
+ @keyframes fadeIn {
47
+ from {
48
+ opacity: 0;
49
+ }
50
+ to {
51
+ opacity: 1;
52
+ }
53
+ }
54
+
55
+ @keyframes fadeOut {
47
56
  from {
48
57
  opacity: 1;
49
58
  }
@@ -80,15 +89,19 @@ const rootCss = css`
80
89
  transition: transform 200ms ease-out;
81
90
  }
82
91
 
92
+ &[data-state="open"] {
93
+ animation: fadeIn 200ms ease-out;
94
+ }
95
+ &[data-state="closed"] {
96
+ animation: fadeOut 200ms ease-out;
97
+ }
98
+
83
99
  @media (prefers-reduced-motion: no-preference) {
84
100
  will-change: transform, opacity;
85
101
 
86
102
  &[data-state="open"] {
87
103
  animation: slideIn 200ms ease-out;
88
104
  }
89
- &[data-state="closed"] {
90
- animation: hide 200ms ease-out;
91
- }
92
105
 
93
106
  &[data-swipe="end"] {
94
107
  animation: swipeOut 200ms ease-out;
@@ -3,6 +3,14 @@ import {ElementRef, forwardRef} from "react";
3
3
  import {ButtonProps} from "../button/button";
4
4
  import {Button} from "../button";
5
5
  import {useTheme} from "../theme-provider";
6
+ import {css} from "@linaria/core";
7
+ import {space} from "../design-system";
8
+
9
+ const toastActionCss = css`
10
+ margin-top: -${space.s4}px;
11
+ margin-bottom: -${space.s4}px;
12
+ flex-shrink: 0;
13
+ `;
6
14
 
7
15
  export const ToastAction = forwardRef<
8
16
  ElementRef<typeof RadixToastAction>,
@@ -10,7 +18,7 @@ export const ToastAction = forwardRef<
10
18
  >(({children, altText, ...props}, ref) => {
11
19
  const theme = useTheme();
12
20
  return (
13
- <RadixToastAction ref={ref} altText={altText} asChild>
21
+ <RadixToastAction className={toastActionCss} ref={ref} altText={altText} asChild>
14
22
  <Button color={theme.textColor} {...props}>
15
23
  {children}
16
24
  </Button>
@@ -0,0 +1,15 @@
1
+ import {createContext} from "@fibery/react/src/create-context";
2
+ import React from "react";
3
+ import type {ToastQueue} from "./toast-queue";
4
+ import {ToastProvider as PrimitiveToastProvider} from "@radix-ui/react-toast";
5
+
6
+ const [Provider, useCtx] = createContext<ToastQueue>("ToastQueue");
7
+
8
+ export const ToastProvider: React.FC<React.PropsWithChildren<{toastQueue: ToastQueue}>> = ({toastQueue, children}) => {
9
+ return (
10
+ <Provider value={toastQueue}>
11
+ <PrimitiveToastProvider>{children}</PrimitiveToastProvider>
12
+ </Provider>
13
+ );
14
+ };
15
+ export const useToast = useCtx;
@@ -1,7 +1,10 @@
1
1
  import {makePubSub} from "@fibery/helpers/utils/pub-sub";
2
2
  import type {ToastProps} from "./toast";
3
3
 
4
- type AddToastProps = Pick<ToastProps, "type" | "title" | "subTitle" | "icon" | "action" | "autoClose" | "open"> & {
4
+ export type AddToastProps = Pick<
5
+ ToastProps,
6
+ "type" | "title" | "subTitle" | "icon" | "action" | "duration" | "open"
7
+ > & {
5
8
  id?: string;
6
9
  };
7
10
  type TypedAddProps = Omit<AddToastProps, "type">;
@@ -12,8 +15,15 @@ type QueuedToast = AddToastProps & {
12
15
 
13
16
  type ToastQueueOpts = {
14
17
  maxActiveToasts?: number;
18
+ onToastAdd?: (params: AddToastProps) => void;
19
+
20
+ /** Overrides toast props.
21
+ * This option is added only to support mapping of billing errors as it was done in toast actions.
22
+ * Ideally this should be removed and billing errors moved to thunk middlewares
23
+ */
24
+ transformToast?: (params: AddToastProps) => AddToastProps;
15
25
  };
16
- export const makeToastQueue = ({maxActiveToasts = 1}: ToastQueueOpts = {}) => {
26
+ export const makeToastQueue = ({maxActiveToasts = 1, onToastAdd, transformToast}: ToastQueueOpts = {}) => {
17
27
  const pubSub = makePubSub();
18
28
  let queue: QueuedToast[] = [];
19
29
  let activeToasts: QueuedToast[] = [];
@@ -35,39 +45,24 @@ export const makeToastQueue = ({maxActiveToasts = 1}: ToastQueueOpts = {}) => {
35
45
  pubSub.publish();
36
46
  };
37
47
 
38
- const closeActive = (id: string) => {
39
- activeToasts = activeToasts.map((t) => (t.id === id ? {...t, open: false} : t));
40
- };
41
-
42
- const removeFromQueue = (id: string) => {
43
- queue = queue.filter((queued) => queued.id !== id);
44
- };
45
-
46
48
  /**
47
49
  * Add toast to queue
48
50
  *
49
51
  * Pass custom `id` if you don't want toasts to be duplicated.
50
52
  * If toast with same id already exists it will be closed to display fresh more relevant one
51
53
  */
52
- const add = ({id, ...toast}: AddToastProps, {replace = true} = {}) => {
53
- if (
54
- id &&
55
- !replace &&
56
- (queue.some((queued) => queued.id === id) || activeToasts.some((active) => active.id === id))
57
- ) {
58
- return id;
59
- }
60
-
61
- if (id && replace) {
62
- closeActive(id);
63
- removeFromQueue(id);
54
+ const add = ({id, ...toast}: AddToastProps) => {
55
+ if (activeToasts.length > 0) {
56
+ activeToasts[0].open = false;
64
57
  }
65
58
 
66
59
  const t: QueuedToast = {
67
60
  id: id || makeId(),
68
- ...toast,
61
+ ...(transformToast ? transformToast(toast) : toast),
69
62
  };
70
63
 
64
+ onToastAdd?.(t);
65
+
71
66
  queue.push(t);
72
67
 
73
68
  updateActiveToasts();
@@ -77,9 +72,10 @@ export const makeToastQueue = ({maxActiveToasts = 1}: ToastQueueOpts = {}) => {
77
72
 
78
73
  return {
79
74
  add,
80
- /** close active toast, play exit animation */
75
+ /** Mark active toast as closed. Can be used to play exit animation */
81
76
  close(id: string) {
82
- closeActive(id);
77
+ activeToasts = activeToasts.map((t) => (t.id === id ? {...t, open: false} : t));
78
+
83
79
  pubSub.publish();
84
80
  },
85
81
  /** remove toast from active ones, should be called after exit animation */