@fibery/ui-kit 1.19.0 → 1.20.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 (51) hide show
  1. package/package.json +7 -5
  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/lists/list-item.tsx +139 -0
  32. package/src/loaders.tsx +10 -10
  33. package/src/loading-sausage.tsx +2 -2
  34. package/src/media-query-utils.ts +1 -1
  35. package/src/select/custom-select-partials/group-heading.tsx +2 -2
  36. package/src/select/custom-select-partials/menu.tsx +9 -1
  37. package/src/select/custom-select-partials/no-option-message.tsx +1 -1
  38. package/src/select/custom-select-partials/option.tsx +64 -7
  39. package/src/select/index.tsx +1 -1
  40. package/src/select/select-in-popover.tsx +10 -10
  41. package/src/select/styles.ts +12 -4
  42. package/src/toast/primitives.tsx +21 -8
  43. package/src/toast/toast-action.tsx +9 -1
  44. package/src/toast/toast-provider.tsx +15 -0
  45. package/src/toast/{toast-queue.tsx → toast-queue.ts} +21 -25
  46. package/src/toast/toast.tsx +35 -28
  47. package/src/toast/toaster.tsx +11 -48
  48. package/src/toast/utils/toastify-item-name.ts +6 -0
  49. package/src/tooltip.tsx +2 -2
  50. package/src/icons/ast/RicheditorMarkHighlight.ts +0 -8
  51. package/src/icons/react/RicheditorMarkHighlight.tsx +0 -12
@@ -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 */
@@ -1,6 +1,7 @@
1
+ import {useCallbackRef} from "@fibery/react/src/use-callback-ref";
1
2
  import {useControllableState} from "@fibery/react/src/use-controllable-state";
2
3
  import {css} from "@linaria/core";
3
- import {useState} from "react";
4
+ import {useEffect, useRef} from "react";
4
5
  import {space, themeVars} from "../design-system";
5
6
  import Spinner from "../icons/react/Spinner";
6
7
  import SuccessIcon from "../icons/react/Success";
@@ -15,21 +16,23 @@ export type ToastProps = {
15
16
  subTitle?: React.ReactNode;
16
17
  icon?: React.ReactNode;
17
18
  action?: ToastActionElement;
18
- autoClose?: boolean;
19
+ duration?: number;
20
+ dismissable?: boolean;
19
21
  open?: boolean;
20
22
  onOpenChange?: (open: boolean) => void;
21
23
 
22
24
  type?: ToastType;
23
25
 
24
- /** Is called on exit animation end */
25
- onRemove?: () => void;
26
+ /** It gets called after exit animation */
27
+ onHide?: () => void;
26
28
  };
27
29
 
28
30
  const defaultPropsPerType: Record<ToastType, Partial<ToastProps>> = {
29
31
  info: {},
30
32
  error: {
31
33
  icon: <WarningTriangle color={themeVars.textColor} />,
32
- autoClose: false,
34
+ duration: 10000,
35
+ dismissable: true,
33
36
  },
34
37
  loading: {
35
38
  icon: <Spinner color={themeVars.textColor} />,
@@ -40,17 +43,19 @@ const defaultPropsPerType: Record<ToastType, Partial<ToastProps>> = {
40
43
  };
41
44
 
42
45
  const rootCss = css`
46
+ box-sizing: border-box;
47
+ min-height: 44px;
43
48
  min-width: 320px;
44
49
  display: flex;
45
50
  align-items: center;
46
- gap: ${space.l}px;
51
+ gap: ${space.s12}px;
47
52
  `;
48
53
 
49
54
  const messageCss = css`
50
55
  display: flex;
51
56
  flex-grow: 1;
52
57
  flex-direction: column;
53
- gap: ${space.xs}px;
58
+ gap: ${space.s4}px;
54
59
  `;
55
60
 
56
61
  const iconCss = css`
@@ -65,10 +70,11 @@ export const Toast: React.FC<ToastProps> = (props) => {
65
70
  subTitle,
66
71
  icon,
67
72
  action,
68
- autoClose = true,
73
+ duration = 5000,
74
+ dismissable,
69
75
  open,
70
76
  onOpenChange,
71
- onRemove,
77
+ onHide,
72
78
  } = {
73
79
  ...defaultProps,
74
80
  ...props,
@@ -76,31 +82,32 @@ export const Toast: React.FC<ToastProps> = (props) => {
76
82
 
77
83
  const [isOpen, setOpen] = useControllableState({value: open, defaultValue: true, onChange: onOpenChange});
78
84
 
79
- const [noReducedMotion] = useState(() => matchMedia("(prefers-reduced-motion: no-preference)").matches);
85
+ const ref = useRef<HTMLLIElement>(null);
86
+ const onHideCb = useCallbackRef(onHide);
80
87
 
81
- const close = autoClose ? null : <ToastClose />;
88
+ useEffect(() => {
89
+ const toastEl = ref.current;
90
+ if (!isOpen) {
91
+ const emergencyTimeout = setTimeout(onHideCb, 400); // just in case if animationend doesn't fire for some reasons or toast got closed before render
82
92
 
83
- const onCloseProps = noReducedMotion
84
- ? {
85
- onOpenChange: setOpen,
93
+ toastEl?.addEventListener("animationend", onHideCb, {
86
94
  // https://github.com/radix-ui/primitives/issues/1020
87
- onAnimationEndCapture() {
88
- if (!isOpen) {
89
- onRemove?.();
90
- }
91
- },
92
- }
93
- : {
94
- onOpenChange(v: boolean) {
95
- setOpen(v);
96
- if (!v) {
97
- onRemove?.();
98
- }
99
- },
95
+ capture: true,
96
+ });
97
+
98
+ return () => {
99
+ toastEl?.removeEventListener("animationend", onHideCb, {capture: true});
100
+ clearTimeout(emergencyTimeout);
100
101
  };
102
+ }
103
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
104
+ return () => {};
105
+ }, [isOpen, onHideCb]);
106
+
107
+ const close = dismissable ? <ToastClose /> : null;
101
108
 
102
109
  return (
103
- <ToastRoot open={open} duration={autoClose ? 5000 : Infinity} className={rootCss} {...onCloseProps}>
110
+ <ToastRoot ref={ref} open={open} duration={duration} className={rootCss} onOpenChange={setOpen}>
104
111
  {icon ? <div className={iconCss}>{icon}</div> : null}
105
112
  <div className={messageCss}>
106
113
  <ToastTitle>{title}</ToastTitle>
@@ -1,13 +1,10 @@
1
- import {createContext} from "@fibery/react/src/create-context";
2
- import React, {useEffect, useSyncExternalStore} from "react";
1
+ import {useSyncExternalStore} from "react";
3
2
  import {createInlineTheme} from "../create-inline-theme";
4
3
  import {colors, getThemeColors} from "../design-system";
5
4
  import {ThemeProvider, useThemeMode} from "../theme-provider";
6
- import {ToastProvider, ToastViewport} from "./primitives";
5
+ import {ToastViewport} from "./primitives";
7
6
  import {Toast} from "./toast";
8
- import type {ToastQueue} from "./toast-queue";
9
- import {useCallbackRef} from "@fibery/react/src/use-callback-ref";
10
- import {reactNodeToString} from "@fibery/react/src/react-node-to-string";
7
+ import {useToast} from "./toast-provider";
11
8
 
12
9
  const ToastThemeProvider: React.FC<React.PropsWithChildren> = ({children}) => {
13
10
  const themeMode = useThemeMode();
@@ -21,52 +18,18 @@ const ToastThemeProvider: React.FC<React.PropsWithChildren> = ({children}) => {
21
18
  );
22
19
  };
23
20
 
24
- const [Provider, useCtx] = createContext<ToastQueue>("ToastQueue");
21
+ export const Toaster: React.FC = () => {
22
+ const toastQueue = useToast();
25
23
 
26
- type OnTrackToast = (args: {title: string; type: string}) => void;
27
- const TrackToast: React.FC<
28
- React.PropsWithChildren<{title: React.ReactNode; type: string; onTrackToast: OnTrackToast}>
29
- > = ({title, type, onTrackToast, children}) => {
30
- const onTrackCb = useCallbackRef(onTrackToast);
31
- useEffect(() => {
32
- onTrackCb({title: reactNodeToString(title), type});
33
- }, [onTrackCb, title, type]);
34
-
35
- return children as JSX.Element;
36
- };
37
-
38
- export const Toaster: React.FC<React.PropsWithChildren<{toastQueue: ToastQueue; onTrackToast: OnTrackToast}>> = ({
39
- toastQueue,
40
- children,
41
- onTrackToast,
42
- }) => {
43
24
  const toasts = useSyncExternalStore(toastQueue.subscribe, toastQueue.getActiveToasts);
44
25
 
45
26
  return (
46
- <Provider value={toastQueue}>
47
- <ToastProvider>
48
- {children}
49
-
50
- <ToastThemeProvider>
51
- {toasts.map(({id, open = true, ...toastProps}) => (
52
- <TrackToast key={id} title={toastProps.title} type={toastProps.type || "info"} onTrackToast={onTrackToast}>
53
- <Toast
54
- {...toastProps}
55
- open={open}
56
- onOpenChange={(v) => {
57
- if (!v) {
58
- toastQueue.close(id);
59
- }
60
- }}
61
- onRemove={() => toastQueue.remove(id)}
62
- />
63
- </TrackToast>
64
- ))}
27
+ <ToastThemeProvider>
28
+ {toasts.map(({id, open, type = "info", ...toastProps}) => (
29
+ <Toast key={`${id}-${type}`} type={type} {...toastProps} open={open} onHide={() => toastQueue.remove(id)} />
30
+ ))}
65
31
 
66
- <ToastViewport />
67
- </ToastThemeProvider>
68
- </ToastProvider>
69
- </Provider>
32
+ <ToastViewport />
33
+ </ToastThemeProvider>
70
34
  );
71
35
  };
72
- export const useToast = useCtx;
@@ -0,0 +1,6 @@
1
+ const charLimit = 60;
2
+
3
+ const trimDynamicToastText = (str: string) => (str.length > charLimit ? `${str.slice(0, 60)}...` : str);
4
+
5
+ export const toastifyItemName = ({prefix, name, postfix}: {prefix?: string; name?: string; postfix?: string}) =>
6
+ `${prefix ? `${prefix} ` : ""}"${name ? trimDynamicToastText(name) : "Untitled"}"${postfix ? ` ${postfix}` : ""}`;
package/src/tooltip.tsx CHANGED
@@ -27,7 +27,7 @@ const tooltipStyle = css`
27
27
  min-height: 30px;
28
28
  max-width: 300px;
29
29
  background-color: ${themeVars.tooltipBgColor};
30
- padding: ${space.s}px ${space.m}px;
30
+ padding: ${space.s6}px ${space.s8}px;
31
31
  border-radius: ${border.radius6}px;
32
32
  box-shadow: ${themeVars.actionMenuShadow};
33
33
 
@@ -132,7 +132,7 @@ export const Tooltip = forwardRef<HTMLButtonElement, TooltipProps>(
132
132
  </>
133
133
  ),
134
134
  side,
135
- sideOffset = space.xs,
135
+ sideOffset = space.s4,
136
136
  align,
137
137
  alignOffset,
138
138
  instant,
@@ -1,8 +0,0 @@
1
-
2
- // This icon file is generated automatically.
3
-
4
- import { IconDefinition } from '../types';
5
-
6
- const RicheditorMarkHighlight: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"viewBox":"0 0 20 20"},"children":[{"type":"element","tagName":"g","properties":{},"children":[{"type":"element","tagName":"path","properties":{"d":"m5.07 16.9.898-.625h2.418c.643 0 1.197-.177 1.66-.607.15-.14.135-.377-.012-.522L5.23 10.438c-.156-.153-.408-.154-.535.023-.34.476-.509 1.087-.509 1.698v2.37l-.89.873c-.382.39-.17.154-.597.624 1.517.595 2.37.874 2.37.874ZM10.653 14.177c.255.25.51.25.764 0l5.997-7.786a1.474 1.474 0 0 0 0-1.746l-1.909-1.87a1.247 1.247 0 0 0-1.781 0l-7.61 6.313c-.255.249-.255.498 0 .748l4.54 4.341Z"},"children":[]}]}],"metadata":""}]},"name":"richeditor-mark-highlight"};
7
-
8
- export default RicheditorMarkHighlight;
@@ -1,12 +0,0 @@
1
- // This icon file is generated automatically.
2
-
3
- import RicheditorMarkHighlightSvg from '../ast/RicheditorMarkHighlight';
4
- import { Icon } from '../Icon';
5
- import { IconBaseProps } from '../types';
6
-
7
- const RicheditorMarkHighlight = (
8
- props: IconBaseProps,
9
- ): JSX.Element => <Icon {...props} icon={RicheditorMarkHighlightSvg} />;
10
-
11
- RicheditorMarkHighlight.displayName = 'RicheditorMarkHighlight';
12
- export default RicheditorMarkHighlight;