@fibery/ui-kit 1.18.0 → 1.19.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 (52) hide show
  1. package/package.json +8 -7
  2. package/src/back-button.tsx +1 -1
  3. package/src/button.tsx +1 -1
  4. package/src/design-system.ts +65 -17
  5. package/src/emoji-picker/emoji-picker-content-with-color.tsx +3 -3
  6. package/src/emoji-picker/primitives/category.tsx +1 -1
  7. package/src/emoji-picker/primitives/search.tsx +1 -1
  8. package/src/emoji-picker/primitives/skin-tone.tsx +1 -1
  9. package/src/error-alert.tsx +1 -1
  10. package/src/icons/Icon.tsx +2 -2
  11. package/src/icons/ast/BellRinging.ts +1 -1
  12. package/src/icons/ast/Clock.ts +8 -0
  13. package/src/icons/ast/ClockAlarm.ts +8 -0
  14. package/src/icons/ast/Copy.ts +1 -1
  15. package/src/icons/ast/ExtensionComments.ts +1 -1
  16. package/src/icons/ast/Monitor.ts +8 -0
  17. package/src/icons/ast/RicheditorCommentCreate.ts +1 -1
  18. package/src/icons/ast/Success.ts +8 -0
  19. package/src/icons/ast/WarningTriangle.ts +8 -0
  20. package/src/icons/ast/index.tsx +5 -0
  21. package/src/icons/react/Clock.tsx +12 -0
  22. package/src/icons/react/ClockAlarm.tsx +12 -0
  23. package/src/icons/react/Monitor.tsx +12 -0
  24. package/src/icons/react/Success.tsx +12 -0
  25. package/src/icons/react/WarningTriangle.tsx +12 -0
  26. package/src/icons/react/index.tsx +5 -0
  27. package/src/{Select → select}/custom-select-partials/clear-indicator.tsx +1 -1
  28. package/src/select/custom-select-partials/option.tsx +65 -0
  29. package/src/{Select → select}/index.tsx +39 -4
  30. package/src/{Select → select}/select-in-popover.tsx +4 -4
  31. package/src/{Select → select}/styles.ts +3 -5
  32. package/src/toast/primitives.tsx +26 -21
  33. package/src/toast/toast-action.tsx +20 -0
  34. package/src/toast/toast-queue.tsx +121 -0
  35. package/src/toast/toast.tsx +114 -0
  36. package/src/toast/toaster.tsx +72 -0
  37. package/src/Select/custom-select-partials/option.tsx +0 -92
  38. package/src/toast/index.tsx +0 -47
  39. /package/src/{Button → button}/actions-button-compact.tsx +0 -0
  40. /package/src/{Button → button}/actions-button.tsx +0 -0
  41. /package/src/{Button → button}/add-button.tsx +0 -0
  42. /package/src/{Button → button}/back-button.tsx +0 -0
  43. /package/src/{Button → button}/button-base.tsx +0 -0
  44. /package/src/{Button → button}/button-group.tsx +0 -0
  45. /package/src/{Button → button}/button.tsx +0 -0
  46. /package/src/{Button → button}/icon-button.tsx +0 -0
  47. /package/src/{Select → select}/custom-select-partials/drop-down-indicator.tsx +0 -0
  48. /package/src/{Select → select}/custom-select-partials/group-heading.tsx +0 -0
  49. /package/src/{Select → select}/custom-select-partials/menu.tsx +0 -0
  50. /package/src/{Select → select}/custom-select-partials/no-option-message.tsx +0 -0
  51. /package/src/{Select → select}/select-control-settings-context.tsx +0 -0
  52. /package/src/{Select → select}/select-loader.tsx +0 -0
@@ -22,12 +22,13 @@ import BaseCreatableSelect, {CreatableProps} from "react-select/creatable";
22
22
  import WindowedSelect, {components, WindowedMenuList} from "react-windowed-select";
23
23
  import {componentsStyles, singleLineComponentsStyle, virtualizedStyles, zIndexStyles} from "./styles";
24
24
  import {useSelectControlSettings} from "./select-control-settings-context";
25
- import {Option} from "./custom-select-partials/option";
25
+ import {Option, OptionSlow} from "./custom-select-partials/option";
26
26
  import {Menu} from "./custom-select-partials/menu";
27
27
  import {GroupHeading} from "./custom-select-partials/group-heading";
28
28
  import {DropdownIndicator} from "./custom-select-partials/drop-down-indicator";
29
29
  import {NoOptionsMessage} from "./custom-select-partials/no-option-message";
30
30
  import {ClearIndicator} from "./custom-select-partials/clear-indicator";
31
+ import {isObject} from "lodash";
31
32
 
32
33
  type GroupBase<T> = ReactSelectGroupBase<T> & {separator?: boolean};
33
34
 
@@ -69,7 +70,6 @@ export type SelectProps<
69
70
  isCollectionMode?: IsMulti;
70
71
  disabled?: boolean;
71
72
  };
72
-
73
73
  export function SingleRowSelect<
74
74
  Option = unknown,
75
75
  IsMulti extends boolean = boolean,
@@ -86,6 +86,23 @@ export function SingleRowSelect<
86
86
  );
87
87
  }
88
88
 
89
+ const FAST_OPTION_USAGE_THRESHOLD = 200;
90
+ function isGroup<Option, Group extends GroupBase<Option>>(optionOrGroup: Option | Group): optionOrGroup is Group {
91
+ return isObject(optionOrGroup) && "options" in optionOrGroup;
92
+ }
93
+ function countOptions<Option, Group extends GroupBase<Option>>(categorizedOptions?: OptionsOrGroups<Option, Group>) {
94
+ if (!categorizedOptions) {
95
+ return 0;
96
+ }
97
+ return categorizedOptions.reduce<number>((optionsAccumulator, categorizedOption) => {
98
+ if (isGroup<Option, Group>(categorizedOption)) {
99
+ return optionsAccumulator + categorizedOption.options.length;
100
+ } else {
101
+ return optionsAccumulator + 1;
102
+ }
103
+ }, 0);
104
+ }
105
+
89
106
  export const Select = forwardRef(function Select<
90
107
  Option = unknown,
91
108
  IsMulti extends boolean = boolean,
@@ -108,6 +125,9 @@ export const Select = forwardRef(function Select<
108
125
  ) {
109
126
  const {getPopupContainerElement, zIndex} = useSelectControlSettings();
110
127
  const [menuOpenState, setMenuOpenState] = useState(rest.defaultMenuIsOpen);
128
+ const optionsCount = useMemo(() => {
129
+ return countOptions<Option, Group>(rest.options);
130
+ }, [rest.options]);
111
131
  const onMenuOpenWrapped = useCallback(() => {
112
132
  setMenuOpenState(true);
113
133
  onMenuOpen && onMenuOpen();
@@ -176,7 +196,13 @@ export const Select = forwardRef(function Select<
176
196
  Menu: Menu,
177
197
  DropdownIndicator,
178
198
  ClearIndicator,
179
- Option,
199
+ /*
200
+ Fast Options break option focus on hover behaviour but help with slow React Select performance with large options list.
201
+ This isolates bug for big lists only
202
+ Read Option definition for more info
203
+ bug here: https://the.fibery.io/SoftDev/bug/Select-option-behaviour-is-incorrect-9413
204
+ */
205
+ Option: optionsCount > FAST_OPTION_USAGE_THRESHOLD ? Option : OptionSlow,
180
206
  MultiValueRemove: ClearIndicator,
181
207
  NoOptionsMessage,
182
208
  GroupHeading,
@@ -222,6 +248,9 @@ export function CreatableSelectInner<
222
248
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
223
249
  ref: any
224
250
  ) {
251
+ const optionsCount = useMemo(() => {
252
+ return countOptions<Option, Group>(rest.options);
253
+ }, [rest.options]);
225
254
  return (
226
255
  <BaseCreatableSelect
227
256
  ref={ref}
@@ -239,7 +268,13 @@ export function CreatableSelectInner<
239
268
  MenuList: virtualized ? WindowedMenuList : reactSelectComponents.MenuList,
240
269
  DropdownIndicator,
241
270
  ClearIndicator,
242
- Option,
271
+ /*
272
+ Fast Options break option focus on hover behaviour but help with slow React Select performance with large options list.
273
+ This isolates bug for big lists only
274
+ Read Option definition for more info
275
+ bug here: https://the.fibery.io/SoftDev/bug/Select-option-behaviour-is-incorrect-9413
276
+ */
277
+ Option: optionsCount > FAST_OPTION_USAGE_THRESHOLD ? Option : OptionSlow,
243
278
  MultiValueRemove: ClearIndicator,
244
279
  NoOptionsMessage,
245
280
  GroupHeading,
@@ -1,5 +1,5 @@
1
1
  import {css, cx} from "@linaria/core";
2
- import {border, radius, space, textStyles} from "../design-system";
2
+ import {border, space, textStyles} from "../design-system";
3
3
  import {inputOverrides} from "../antd/styles";
4
4
  import React, {
5
5
  ComponentType,
@@ -12,7 +12,7 @@ import React, {
12
12
  useRef,
13
13
  useState,
14
14
  } from "react";
15
- import {Popup} from "../Popup";
15
+ import {Popup} from "../popup";
16
16
  import {$TSFixMe} from "../tsfixme";
17
17
  import {
18
18
  combineStyles,
@@ -38,7 +38,7 @@ const popupClassName = css`
38
38
  `;
39
39
 
40
40
  const valueClassName = css`
41
- border-radius: ${radius}px;
41
+ border-radius: ${border.radius4}px;
42
42
 
43
43
  &:focus {
44
44
  outline: none;
@@ -81,7 +81,7 @@ export const selectInPopupStyles = {
81
81
  marginTop: space.s,
82
82
  marginBottom: 0,
83
83
  boxShadow: "none",
84
- borderRadius: radius,
84
+ borderRadius: border.radius4,
85
85
  }),
86
86
  };
87
87
 
@@ -2,7 +2,7 @@ import {css} from "@linaria/core";
2
2
  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
- import {OptionNulledStyles} from "./custom-select-partials/option";
5
+ import {OptionNulledStyles, OptionNulledVitualizedStyles} from "./custom-select-partials/option";
6
6
  import {MenuNulledStyles} from "./custom-select-partials/menu";
7
7
  import {groupHeadingNulledStyles} from "./custom-select-partials/group-heading";
8
8
 
@@ -42,7 +42,7 @@ function createControlStyle({
42
42
  }
43
43
 
44
44
  export const virtualizedStyles: StylesConfig = {
45
- option: () => OptionNulledStyles,
45
+ option: () => ({...OptionNulledVitualizedStyles, ...OptionNulledStyles}),
46
46
  groupHeading: (provided) => ({
47
47
  ...provided,
48
48
  height: layout.menuItemHeight,
@@ -64,9 +64,7 @@ export const zIndexStyles = (zIndex: number): StylesConfig => ({
64
64
  });
65
65
 
66
66
  export const componentsStyles: StylesConfig = {
67
- option: () => ({
68
- minHeight: layout.menuItemHeight,
69
- }),
67
+ option: () => OptionNulledStyles,
70
68
  placeholder: (provided) => ({
71
69
  ...provided,
72
70
  color: themeVars.disabledTextColor,
@@ -1,24 +1,31 @@
1
+ import {preventDefault} from "@fibery/react/src/prevent-default";
1
2
  import {css, cx} from "@linaria/core";
2
3
  import * as ToastPrimitives from "@radix-ui/react-toast";
3
4
  import {ElementRef, forwardRef} from "react";
5
+ import {IconButton} from "../button/icon-button";
4
6
  import {border, space, textStyles, themeVars} from "../design-system";
5
7
  import CloseIcon from "../icons/react/Close";
6
- import {IconButton} from "../Button/icon-button";
7
- import {Button} from "../button";
8
- import {ButtonProps} from "../Button/button";
9
- import {useTheme} from "../theme-provider";
10
8
 
11
9
  export const ToastProvider = ToastPrimitives.Provider;
12
10
 
13
11
  const viewportCss = css`
14
12
  position: fixed;
15
13
  left: 50%;
16
- transform: translateX(-50%);
17
14
  bottom: 0;
15
+ transform: translateX(-50%);
16
+
17
+ display: flex;
18
+ flex-direction: column-reverse;
19
+ align-items: center;
20
+ gap: ${space.s16}px;
18
21
  margin: 0;
19
22
  z-index: 1010; // inherited from old toast implementation
20
- padding: ${space.xl}px;
23
+ padding: 0;
21
24
  list-style: none;
25
+
26
+ &:not(:empty) {
27
+ padding: ${space.xl}px; // increased mouse hover area to pause timers of toast
28
+ }
22
29
  `;
23
30
 
24
31
  export const ToastViewport = forwardRef<
@@ -29,6 +36,8 @@ export const ToastViewport = forwardRef<
29
36
  });
30
37
 
31
38
  const rootCss = css`
39
+ user-select: auto !important;
40
+
32
41
  background-color: ${themeVars.colorBgToastDefault};
33
42
  padding: ${space.m}px ${space.m * 2}px;
34
43
  border-radius: ${border.radius6}px;
@@ -89,7 +98,17 @@ const rootCss = css`
89
98
 
90
99
  export const ToastRoot = forwardRef<ElementRef<typeof ToastPrimitives.Root>, ToastPrimitives.ToastProps>(
91
100
  ({className, ...props}, ref) => {
92
- return <ToastPrimitives.Root ref={ref} className={cx(rootCss, className)} {...props}></ToastPrimitives.Root>;
101
+ return (
102
+ <ToastPrimitives.Root
103
+ ref={ref}
104
+ className={cx(rootCss, className)}
105
+ onSwipeStart={preventDefault}
106
+ onSwipeMove={preventDefault}
107
+ onSwipeCancel={preventDefault}
108
+ onSwipeEnd={preventDefault}
109
+ {...props}
110
+ />
111
+ );
93
112
  }
94
113
  );
95
114
 
@@ -115,20 +134,6 @@ export const ToastSubtitle = forwardRef<
115
134
  return <ToastPrimitives.Description ref={ref} className={cx(className, descriptionCss)} {...props} />;
116
135
  });
117
136
 
118
- export const ToastAction = forwardRef<
119
- ElementRef<typeof ToastPrimitives.Action>,
120
- ButtonProps & {altText: ToastPrimitives.ToastActionProps["altText"]}
121
- >(({children, altText, ...props}, ref) => {
122
- const theme = useTheme();
123
- return (
124
- <ToastPrimitives.Action ref={ref} altText={altText} asChild>
125
- <Button color={theme.textColor} {...props}>
126
- {children}
127
- </Button>
128
- </ToastPrimitives.Action>
129
- );
130
- });
131
-
132
137
  export const ToastClose = () => {
133
138
  return (
134
139
  <ToastPrimitives.Close aria-label="Dismiss" asChild>
@@ -0,0 +1,20 @@
1
+ import {Action as RadixToastAction, type ToastActionProps} from "@radix-ui/react-toast";
2
+ import {ElementRef, forwardRef} from "react";
3
+ import {ButtonProps} from "../button/button";
4
+ import {Button} from "../button";
5
+ import {useTheme} from "../theme-provider";
6
+
7
+ export const ToastAction = forwardRef<
8
+ ElementRef<typeof RadixToastAction>,
9
+ ButtonProps & {altText: ToastActionProps["altText"]}
10
+ >(({children, altText, ...props}, ref) => {
11
+ const theme = useTheme();
12
+ return (
13
+ <RadixToastAction ref={ref} altText={altText} asChild>
14
+ <Button color={theme.textColor} {...props}>
15
+ {children}
16
+ </Button>
17
+ </RadixToastAction>
18
+ );
19
+ });
20
+ export type ToastActionElement = React.ReactElement<typeof ToastAction>;
@@ -0,0 +1,121 @@
1
+ import {makePubSub} from "@fibery/helpers/utils/pub-sub";
2
+ import type {ToastProps} from "./toast";
3
+
4
+ type AddToastProps = Pick<ToastProps, "type" | "title" | "subTitle" | "icon" | "action" | "autoClose" | "open"> & {
5
+ id?: string;
6
+ };
7
+ type TypedAddProps = Omit<AddToastProps, "type">;
8
+
9
+ type QueuedToast = AddToastProps & {
10
+ id: string;
11
+ };
12
+
13
+ type ToastQueueOpts = {
14
+ maxActiveToasts?: number;
15
+ };
16
+ export const makeToastQueue = ({maxActiveToasts = 1}: ToastQueueOpts = {}) => {
17
+ const pubSub = makePubSub();
18
+ let queue: QueuedToast[] = [];
19
+ let activeToasts: QueuedToast[] = [];
20
+
21
+ let count = 0;
22
+
23
+ const makeId = () => {
24
+ return `toast-${count++}`;
25
+ };
26
+
27
+ const updateActiveToasts = () => {
28
+ const freeSlots = maxActiveToasts - activeToasts.length;
29
+
30
+ const newToasts = queue.slice(0, freeSlots);
31
+ queue = queue.slice(freeSlots);
32
+
33
+ activeToasts = activeToasts.concat(newToasts);
34
+
35
+ pubSub.publish();
36
+ };
37
+
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
+ /**
47
+ * Add toast to queue
48
+ *
49
+ * Pass custom `id` if you don't want toasts to be duplicated.
50
+ * If toast with same id already exists it will be closed to display fresh more relevant one
51
+ */
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);
64
+ }
65
+
66
+ const t: QueuedToast = {
67
+ id: id || makeId(),
68
+ ...toast,
69
+ };
70
+
71
+ queue.push(t);
72
+
73
+ updateActiveToasts();
74
+
75
+ return t.id;
76
+ };
77
+
78
+ return {
79
+ add,
80
+ /** close active toast, play exit animation */
81
+ close(id: string) {
82
+ closeActive(id);
83
+ pubSub.publish();
84
+ },
85
+ /** remove toast from active ones, should be called after exit animation */
86
+ remove(id: string) {
87
+ activeToasts = activeToasts.filter((t) => t.id !== id);
88
+
89
+ updateActiveToasts();
90
+ },
91
+ getActiveToasts() {
92
+ return activeToasts;
93
+ },
94
+ subscribe: pubSub.subscribe,
95
+ unsubscribe: pubSub.unsubscribe,
96
+
97
+ info(t: TypedAddProps) {
98
+ return add(t);
99
+ },
100
+ success(t: TypedAddProps) {
101
+ return add({
102
+ type: "success",
103
+ ...t,
104
+ });
105
+ },
106
+ error(t: TypedAddProps) {
107
+ return add({
108
+ type: "error",
109
+ ...t,
110
+ });
111
+ },
112
+ loading(t: TypedAddProps) {
113
+ return add({
114
+ type: "loading",
115
+ ...t,
116
+ });
117
+ },
118
+ };
119
+ };
120
+
121
+ export type ToastQueue = ReturnType<typeof makeToastQueue>;
@@ -0,0 +1,114 @@
1
+ import {useControllableState} from "@fibery/react/src/use-controllable-state";
2
+ import {css} from "@linaria/core";
3
+ import {useState} from "react";
4
+ import {space, themeVars} from "../design-system";
5
+ import Spinner from "../icons/react/Spinner";
6
+ import SuccessIcon from "../icons/react/Success";
7
+ import WarningTriangle from "../icons/react/WarningTriangle";
8
+ import {ToastClose, ToastRoot, ToastSubtitle, ToastTitle} from "./primitives";
9
+ import type {ToastActionElement} from "./toast-action";
10
+
11
+ type ToastType = "info" | "error" | "success" | "loading";
12
+
13
+ export type ToastProps = {
14
+ title: React.ReactNode;
15
+ subTitle?: React.ReactNode;
16
+ icon?: React.ReactNode;
17
+ action?: ToastActionElement;
18
+ autoClose?: boolean;
19
+ open?: boolean;
20
+ onOpenChange?: (open: boolean) => void;
21
+
22
+ type?: ToastType;
23
+
24
+ /** Is called on exit animation end */
25
+ onRemove?: () => void;
26
+ };
27
+
28
+ const defaultPropsPerType: Record<ToastType, Partial<ToastProps>> = {
29
+ info: {},
30
+ error: {
31
+ icon: <WarningTriangle color={themeVars.textColor} />,
32
+ autoClose: false,
33
+ },
34
+ loading: {
35
+ icon: <Spinner color={themeVars.textColor} />,
36
+ },
37
+ success: {
38
+ icon: <SuccessIcon color={themeVars.textColor} />,
39
+ },
40
+ };
41
+
42
+ const rootCss = css`
43
+ min-width: 320px;
44
+ display: flex;
45
+ align-items: center;
46
+ gap: ${space.l}px;
47
+ `;
48
+
49
+ const messageCss = css`
50
+ display: flex;
51
+ flex-grow: 1;
52
+ flex-direction: column;
53
+ gap: ${space.xs}px;
54
+ `;
55
+
56
+ const iconCss = css`
57
+ grid-area: icon;
58
+ `;
59
+
60
+ export const Toast: React.FC<ToastProps> = (props) => {
61
+ const defaultProps = defaultPropsPerType[props.type || "info"];
62
+
63
+ const {
64
+ title,
65
+ subTitle,
66
+ icon,
67
+ action,
68
+ autoClose = true,
69
+ open,
70
+ onOpenChange,
71
+ onRemove,
72
+ } = {
73
+ ...defaultProps,
74
+ ...props,
75
+ };
76
+
77
+ const [isOpen, setOpen] = useControllableState({value: open, defaultValue: true, onChange: onOpenChange});
78
+
79
+ const [noReducedMotion] = useState(() => matchMedia("(prefers-reduced-motion: no-preference)").matches);
80
+
81
+ const close = autoClose ? null : <ToastClose />;
82
+
83
+ const onCloseProps = noReducedMotion
84
+ ? {
85
+ onOpenChange: setOpen,
86
+ // 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
+ },
100
+ };
101
+
102
+ return (
103
+ <ToastRoot open={open} duration={autoClose ? 5000 : Infinity} className={rootCss} {...onCloseProps}>
104
+ {icon ? <div className={iconCss}>{icon}</div> : null}
105
+ <div className={messageCss}>
106
+ <ToastTitle>{title}</ToastTitle>
107
+ {subTitle ? <ToastSubtitle>{subTitle}</ToastSubtitle> : null}
108
+ </div>
109
+
110
+ {action}
111
+ {close}
112
+ </ToastRoot>
113
+ );
114
+ };
@@ -0,0 +1,72 @@
1
+ import {createContext} from "@fibery/react/src/create-context";
2
+ import React, {useEffect, useSyncExternalStore} from "react";
3
+ import {createInlineTheme} from "../create-inline-theme";
4
+ import {colors, getThemeColors} from "../design-system";
5
+ import {ThemeProvider, useThemeMode} from "../theme-provider";
6
+ import {ToastProvider, ToastViewport} from "./primitives";
7
+ 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";
11
+
12
+ const ToastThemeProvider: React.FC<React.PropsWithChildren> = ({children}) => {
13
+ const themeMode = useThemeMode();
14
+ const theme = getThemeColors(colors.brandColors.blue, themeMode === "dark" ? "light" : "dark");
15
+ const inlineTheme = createInlineTheme(theme, []);
16
+
17
+ return (
18
+ <ThemeProvider theme={theme}>
19
+ <div style={inlineTheme}>{children}</div>
20
+ </ThemeProvider>
21
+ );
22
+ };
23
+
24
+ const [Provider, useCtx] = createContext<ToastQueue>("ToastQueue");
25
+
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
+ const toasts = useSyncExternalStore(toastQueue.subscribe, toastQueue.getActiveToasts);
44
+
45
+ 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
+ ))}
65
+
66
+ <ToastViewport />
67
+ </ToastThemeProvider>
68
+ </ToastProvider>
69
+ </Provider>
70
+ );
71
+ };
72
+ export const useToast = useCtx;
@@ -1,92 +0,0 @@
1
- import {OptionProps} from "react-select";
2
- import {components} from "react-windowed-select";
3
- import {GroupBase} from "../index";
4
- import {css, cx} from "@linaria/core";
5
- import {border, layout, space, textStyles, themeVars, transition} from "../../design-system";
6
- import {useMemo} from "react";
7
-
8
- export const OptionNulledStyles = {
9
- height: layout.menuItemHeight,
10
- };
11
- const OptionWrapperClass = css`
12
- max-width: 100%;
13
- flex-basis: 100%;
14
- display: flex;
15
- align-items: center;
16
- align-content: center;
17
- ${{...textStyles.regular}};
18
- color: ${themeVars.textColor};
19
- background: ${themeVars.colorBgSelectOptionDefault};
20
- border-radius: ${border.radius6}px;
21
- padding: 0 ${space.s}px;
22
- column-gap: ${space.xs}px;
23
- transition: background-color ${transition}, color ${transition};
24
- `;
25
- const OptionRootClass = css`
26
- padding: 0 ${space.s}px ${space.xxs}px;
27
- display: flex;
28
- align-items: stretch;
29
- justify-content: stretch;
30
- cursor: pointer;
31
- &:hover {
32
- .${OptionWrapperClass} {
33
- background: ${themeVars.colorBgSelectOptionDefaultHover};
34
- }
35
- }
36
- `;
37
-
38
- const DisabledOptionClass = css`
39
- cursor: not-allowed;
40
- .${OptionWrapperClass} {
41
- > * {
42
- opacity: ${themeVars.opacitySelectOptionDisabled};
43
- }
44
- background: ${themeVars.colorBgSelectOptionDisabled};
45
- }
46
- &:hover {
47
- .${OptionWrapperClass} {
48
- background: ${themeVars.colorBgSelectOptionDisabled};
49
- }
50
- }
51
- `;
52
- const SelectedOptionClass = css`
53
- cursor: default;
54
- .${OptionWrapperClass} {
55
- background: ${themeVars.colorBgSelectOptionSelected};
56
- }
57
- &:hover {
58
- .${OptionWrapperClass} {
59
- background: ${themeVars.colorBgSelectOptionSelectedHover};
60
- }
61
- }
62
- &.${DisabledOptionClass} {
63
- background: ${themeVars.colorBgSelectOptionSelectedDisabled};
64
- &:hover {
65
- background: ${themeVars.colorBgSelectOptionSelectedDisabled};
66
- }
67
- }
68
- `;
69
- export function Option<Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>>(
70
- props: OptionProps<Option, IsMulti, Group>
71
- ) {
72
- const {children, ...rest} = props;
73
- const {isDisabled, isFocused} = rest;
74
- const className = useMemo(
75
- () =>
76
- cx(
77
- rest.innerProps.className,
78
- OptionRootClass,
79
- isFocused && SelectedOptionClass, //do not use isSelected as flag to determine if item is selected. It is not always true, bug
80
- isDisabled && DisabledOptionClass
81
- ),
82
- [rest.innerProps.className, isDisabled, isFocused]
83
- );
84
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
85
- const {onMouseMove, onMouseOver, ...truncatedProps} = rest.innerProps;
86
- const newProps = Object.assign(rest, {innerProps: truncatedProps});
87
- return (
88
- <components.Option {...newProps} className={className}>
89
- <div className={OptionWrapperClass}>{children}</div>
90
- </components.Option>
91
- );
92
- }