@nuskin/foundation-ui-components 1.24.0 → 1.24.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/foundation-ui-components",
3
- "version": "1.24.0",
3
+ "version": "1.24.1",
4
4
  "description": "It is a foundation library written in react, that provides common components to all UI pages",
5
5
  "main": "src/common/index.ts",
6
6
  "repository": {
@@ -52,6 +52,7 @@
52
52
  "cliui": "^8.0.1",
53
53
  "framer-motion": "^10.16.4",
54
54
  "next": "13.5.6",
55
+ "notistack": "^3.0.1",
55
56
  "react": "18.2.0",
56
57
  "react-dom": "^18.2.0",
57
58
  "react-material-ui-carousel": "^3.4.2",
@@ -12,20 +12,20 @@ export function generateContainedStyle({
12
12
  hovered,
13
13
  pressed,
14
14
  focused,
15
- }: ContainedParams): string {
16
- return `
17
- background-color: ${backgroundColor};
18
- color: ${textColor} !important;
19
- &:hover {
20
- background-color: ${hovered};
21
- }
22
- &:active {
23
- background-color: ${pressed};
24
- }
25
- &:focus {
26
- background-color: ${focused};
27
- }
28
- `;
15
+ }: ContainedParams): Record<string, any> {
16
+ return {
17
+ backgroundColor,
18
+ color: `${textColor} !important`,
19
+ '&:hover': {
20
+ backgroundColor: `${hovered}`
21
+ },
22
+ '&:active': {
23
+ backgroundColor: `${pressed}`
24
+ },
25
+ '&:focus': {
26
+ backgroundColor: `${focused}`
27
+ }
28
+ };
29
29
  }
30
30
  type OutlinedParams = {
31
31
  borderColor: string;
@@ -34,18 +34,18 @@ type OutlinedParams = {
34
34
  activeColor: string;
35
35
  };
36
36
  export function generateOutlinedStyle({ borderColor, hovered, pressed, activeColor }: OutlinedParams) {
37
- return `
38
- border-color: ${borderColor};
39
- color: ${borderColor};
40
- &:hover {
41
- border-color: ${hovered};
42
- color: ${borderColor};
43
- }
44
- &:active {
45
- border-color: ${pressed};
46
- color: ${activeColor};
47
- }
48
- `;
37
+ return {
38
+ borderColor: `${borderColor}`,
39
+ color: `${borderColor}`,
40
+ '&:hover': {
41
+ borderColor: `${hovered}`,
42
+ color: `${borderColor}`
43
+ },
44
+ '&:active': {
45
+ borderColor: `${pressed}`,
46
+ color: `${activeColor}`
47
+ }
48
+ }
49
49
  }
50
50
 
51
51
  type TextParams = {
@@ -54,29 +54,31 @@ type TextParams = {
54
54
  pressed: string;
55
55
  };
56
56
 
57
- export function generateTextStyles({ underlineColor, hovered, pressed }: TextParams): string {
58
- return `
59
- text-decoration: underline 1px !important;
60
- text-underline-offset: 4px;
61
- padding: 0;
62
- min-width: auto;
63
- background-color: transparent !important;
64
- text-decoration-color: ${underlineColor};
65
- color: ${underlineColor};
66
- &:hover {
67
- color: ${hovered};
68
- text-decoration-color: ${hovered};
69
- }
70
- &:active {
71
- color: ${underlineColor};
72
- text-decoration-color: ${pressed};
73
- }
74
- `;
57
+ export function generateTextStyles({ underlineColor, hovered, pressed }: TextParams): Record<string, any> {
58
+ return {
59
+ textDecoration: 'underline 1px !important',
60
+ textUnderlineOffset: '4px',
61
+ minWidth: 'auto',
62
+ backgroundColor: 'transparent !important',
63
+ textDecorationColor: `${underlineColor}`,
64
+ color: `${underlineColor}`,
65
+ '&:hover': {
66
+ color: `${hovered}`,
67
+ textDecorationColor: `${hovered}`,
68
+ backgroundColor: 'transparent !important',
69
+ border: 0,
70
+ },
71
+ '&:active, &:focus': {
72
+ color: `${underlineColor}`,
73
+ textDecorationColor: `${pressed}`,
74
+ border: 'none !important',
75
+ }
76
+ };
75
77
  }
76
78
 
77
79
  export function generateIconOnlyStyles() {
78
- return `
79
- padding: 10px;
80
- min-width: unset;
81
- `;
80
+ return {
81
+ padding: '10px',
82
+ minWidth: 'unset',
83
+ };
82
84
  }
@@ -31,6 +31,7 @@ export * from './select';
31
31
  export * from './spinner';
32
32
  export * from './table';
33
33
  export * from './text-strip';
34
+ export * from './toast';
34
35
  export * from './typography';
35
36
  export * from './utility';
36
37
  export * from './videos';
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Credit to MUI team @ https://mui.com
3
+ */
4
+ import * as React from 'react';
5
+ import { TransitionProps, TransitionStatus, Transition } from 'notistack';
6
+ import {
7
+ useForkRef,
8
+ reflow,
9
+ getTransitionProps,
10
+ createTransition,
11
+ } from './shared';
12
+
13
+ const styles: Partial<Record<TransitionStatus, React.CSSProperties>> = {
14
+ entering: {
15
+ opacity: 1,
16
+ },
17
+ entered: {
18
+ opacity: 1,
19
+ },
20
+ };
21
+
22
+ const Fade = React.forwardRef<HTMLDivElement, TransitionProps>((props, ref) => {
23
+ const {
24
+ children,
25
+ in: inProp,
26
+ timeout = 0,
27
+ style,
28
+ onEnter,
29
+ onEntered,
30
+ onExit,
31
+ onExited,
32
+ direction, // Take this out since this is a Slide-only prop
33
+ ...other
34
+ } = props;
35
+
36
+ const nodeRef = React.useRef<HTMLDivElement>(null);
37
+ const handleRefIntermediary = useForkRef((children as any).ref, ref);
38
+ const handleRef = useForkRef(nodeRef, handleRefIntermediary);
39
+
40
+ const handleEnter: TransitionProps['onEnter'] = (node, isAppearing) => {
41
+ reflow(node);
42
+
43
+ const transitionProps = getTransitionProps({
44
+ style,
45
+ timeout,
46
+ mode: 'enter',
47
+ });
48
+ node.style.webkitTransition = createTransition('opacity', transitionProps);
49
+ node.style.transition = createTransition('opacity', transitionProps);
50
+
51
+ if (onEnter) {
52
+ onEnter(node, isAppearing);
53
+ }
54
+ };
55
+
56
+ const handleExit: TransitionProps['onExit'] = (node) => {
57
+ const transitionProps = getTransitionProps({
58
+ style,
59
+ timeout,
60
+ mode: 'exit',
61
+ });
62
+ node.style.webkitTransition = createTransition('opacity', transitionProps);
63
+ node.style.transition = createTransition('opacity', transitionProps);
64
+
65
+ if (onExit) {
66
+ onExit(node);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <Transition
72
+ appear
73
+ in={inProp}
74
+ nodeRef={nodeRef}
75
+ onEnter={handleEnter}
76
+ onEntered={onEntered}
77
+ onExit={handleExit}
78
+ onExited={onExited}
79
+ timeout={timeout}
80
+ {...other}
81
+ >
82
+ {(status, childProps) =>
83
+ React.cloneElement(children as any, {
84
+ style: {
85
+ opacity: 0,
86
+ visibility: status === 'exited' && !inProp ? 'hidden' : undefined,
87
+ ...styles[status],
88
+ ...style,
89
+ ...(children as any).props.style,
90
+ },
91
+ ref: handleRef,
92
+ ...childProps,
93
+ })
94
+ }
95
+ </Transition>
96
+ );
97
+ });
98
+
99
+ Fade.displayName = 'Fade';
100
+
101
+ export default Fade;
@@ -0,0 +1,76 @@
1
+ import type { PropsWithChildren } from 'react';
2
+ import { SnackbarProvider, SnackbarProviderProps, MaterialDesignContent } from 'notistack';
3
+
4
+ import { styled, ColorUtils, ScreenSize } from '@nuskin/foundation-theme';
5
+
6
+ const { getGenomeColor } = ColorUtils;
7
+
8
+ const ToastNotification = styled(MaterialDesignContent)`
9
+ width: 610px;
10
+ height: 46px;
11
+ box-shadow: none;
12
+ padding: 0 20px;
13
+
14
+ @media (max-width: ${ScreenSize.md}px) {
15
+ width: 343px;
16
+ }
17
+
18
+ #notistack-snackbar svg {
19
+ font-size: 24px !important;
20
+ }
21
+ &.notistack-MuiContent-success {
22
+ background-color: ${({ theme }) => getGenomeColor(theme, 'Success-10')};
23
+ border: 1px solid ${({ theme }) => getGenomeColor(theme, 'Success-30')};
24
+ color: ${({ theme }) => getGenomeColor(theme, 'Success-80')};
25
+
26
+ svg g {
27
+ stroke: ${({ theme }) => getGenomeColor(theme, 'Success-80')};
28
+ }
29
+ }
30
+ &.notistack-MuiContent-error {
31
+ background-color: ${({ theme }) => getGenomeColor(theme, 'Error-10')};
32
+ border: 1px solid ${({ theme }) => getGenomeColor(theme, 'Error-50')};
33
+ color: ${({ theme }) => getGenomeColor(theme, 'Error-60')};
34
+
35
+ svg g {
36
+ stroke: ${({ theme }) => getGenomeColor(theme, 'Error-60')};
37
+ }
38
+ }
39
+ &.notistack-MuiContent-warning {
40
+ background-color: ${({ theme }) => getGenomeColor(theme, 'Warning-10')};
41
+ border: 1px solid ${({ theme }) => getGenomeColor(theme, 'Warning-40')};
42
+ color: ${({ theme }) => getGenomeColor(theme, 'Warning-80')};
43
+
44
+ svg g {
45
+ stroke: ${({ theme }) => getGenomeColor(theme, 'Warning-80')};
46
+ }
47
+ }
48
+ &.notistack-MuiContent-info {
49
+ background-color: ${({ theme }) => getGenomeColor(theme, 'Info-10')};
50
+ border: 1px solid ${({ theme }) => getGenomeColor(theme, 'Info-40')};
51
+ color: ${({ theme }) => getGenomeColor(theme, 'Info-60')};
52
+
53
+ svg g {
54
+ stroke: ${({ theme }) => getGenomeColor(theme, 'Info-60')};
55
+ }
56
+ }
57
+ `;
58
+
59
+ const NsToastProvider = (props: PropsWithChildren<SnackbarProviderProps>) => {
60
+ const {
61
+ children,
62
+ autoHideDuration = 5000
63
+ } = props;
64
+ return (
65
+ <SnackbarProvider autoHideDuration={autoHideDuration} Components={{
66
+ success: ToastNotification,
67
+ error: ToastNotification,
68
+ info: ToastNotification,
69
+ warning: ToastNotification,
70
+ }}>
71
+ { children }
72
+ </SnackbarProvider>
73
+ );
74
+ };
75
+
76
+ export default NsToastProvider;
@@ -0,0 +1,3 @@
1
+ export { default as NsToastProvider } from './ToastProvider';
2
+ export { default as useToast } from './useToast';
3
+ export type { ToastOptions } from './useToast';
@@ -0,0 +1,124 @@
1
+ import { TransitionDuration } from 'notistack';
2
+ import React from 'react';
3
+
4
+ /**
5
+ * passes {value} to {ref}
6
+ *
7
+ * Useful if you want to expose the ref of an inner component to the public API
8
+ * while still using it inside the component.
9
+ * @param ref A ref callback or ref object. If anything falsy, this is a no-op.
10
+ */
11
+ function setRef<T>(
12
+ ref:
13
+ | React.MutableRefObject<T | null>
14
+ | ((instance: T | null) => void)
15
+ | null
16
+ | undefined,
17
+ value: T | null
18
+ ): void {
19
+ if (typeof ref === 'function') {
20
+ ref(value);
21
+ } else if (ref) {
22
+ ref.current = value;
23
+ }
24
+ }
25
+
26
+ export function useForkRef<Instance>(
27
+ refA: React.Ref<Instance> | null | undefined,
28
+ refB: React.Ref<Instance> | null | undefined
29
+ ): React.Ref<Instance> | null {
30
+ /**
31
+ * This will create a new function if the ref props change and are defined.
32
+ * This means react will call the old forkRef with `null` and the new forkRef
33
+ * with the ref. Cleanup naturally emerges from this behavior.
34
+ */
35
+ return React.useMemo(() => {
36
+ if (refA == null && refB == null) {
37
+ return null;
38
+ }
39
+ return (refValue) => {
40
+ setRef(refA, refValue);
41
+ setRef(refB, refValue);
42
+ };
43
+ }, [refA, refB]);
44
+ }
45
+
46
+ interface ComponentProps {
47
+ style?: React.CSSProperties | undefined;
48
+ /**
49
+ * number: e.g. 400
50
+ * TransitionDuration: e.g. { enter: 200, exit: 400 }
51
+ */
52
+ timeout: number | TransitionDuration;
53
+ mode: 'enter' | 'exit';
54
+ }
55
+
56
+ interface TransitionPropsReturnType {
57
+ duration: number;
58
+ easing: string | undefined;
59
+ delay: string | undefined;
60
+ }
61
+
62
+ export function getTransitionProps(
63
+ props: ComponentProps
64
+ ): TransitionPropsReturnType {
65
+ const { timeout, style = {}, mode } = props;
66
+ return {
67
+ duration: typeof timeout === 'object' ? timeout[mode] || 0 : timeout,
68
+ easing: style.transitionTimingFunction,
69
+ delay: style.transitionDelay,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * CSS hack to force a repaint
75
+ */
76
+ export const reflow = (node: Element): void => {
77
+ // We have to do something with node.scrollTop.
78
+ // Otherwise it's removed from the compiled code by optimisers
79
+ // eslint-disable-next-line no-self-assign
80
+ node.scrollTop = node.scrollTop;
81
+ };
82
+
83
+ interface CreateTransitionOptions {
84
+ duration: number;
85
+ easing?: string;
86
+ delay?: string | number;
87
+ }
88
+
89
+ const defaultEasing = {
90
+ // This is the most common easing curve.
91
+ easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
92
+ // Objects enter the screen at full velocity from off-screen and
93
+ // slowly decelerate to a resting point.
94
+ easeOut: 'cubic-bezier(0.0, 0, 0.2, 1)',
95
+ // Objects leave the screen at full velocity. They do not decelerate when off-screen.
96
+ easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
97
+ // The sharp curve is used by objects that may return to the screen at any time.
98
+ sharp: 'cubic-bezier(0.4, 0, 0.6, 1)',
99
+ };
100
+
101
+ const formatMs = (milliseconds: number) => `${Math.round(milliseconds)}ms`;
102
+
103
+ export function createTransition(
104
+ props: string | string[] = ['all'],
105
+ options?: CreateTransitionOptions
106
+ ): string {
107
+ const {
108
+ duration = 300,
109
+ easing = defaultEasing.easeInOut,
110
+ delay = 0,
111
+ } = options || {};
112
+
113
+ const properties = Array.isArray(props) ? props : [props];
114
+
115
+ return properties
116
+ .map((animatedProp) => {
117
+ const formattedDuration =
118
+ typeof duration === 'string' ? duration : formatMs(duration);
119
+ const formattedDelay =
120
+ typeof delay === 'string' ? delay : formatMs(delay);
121
+ return `${animatedProp} ${formattedDuration} ${easing} ${formattedDelay}`;
122
+ })
123
+ .join(',');
124
+ }
@@ -0,0 +1,130 @@
1
+ import { VariantType, closeSnackbar } from 'notistack';
2
+ import { render, screen } from '@/test-utils/index';
3
+ import React, { PropsWithChildren, useEffect } from 'react';
4
+
5
+ import { NsToastProvider, ToastOptions, useToast } from '../';
6
+
7
+ const ToastWrapper = ({ children }: PropsWithChildren<{}>) => {
8
+ return (
9
+ <NsToastProvider>
10
+ { children }
11
+ </NsToastProvider>
12
+ );
13
+ }
14
+
15
+ const ToastComponent = (props: {
16
+ variant: VariantType;
17
+ message?: string;
18
+ options?: ToastOptions;
19
+ }) => {
20
+ const { showToast } = useToast();
21
+ const {
22
+ variant,
23
+ message,
24
+ options,
25
+ } = props;
26
+ useEffect(() => {
27
+ showToast(variant, message, options);
28
+ // eslint-disable-next-line react-hooks/exhaustive-deps
29
+ }, []);
30
+ return (
31
+ <div></div>
32
+ );
33
+ };
34
+
35
+ describe('Toast', () => {
36
+ afterEach(() => {
37
+ closeSnackbar();
38
+ });
39
+ it('should render a toast and displays the message', () => {
40
+ render(
41
+ <ToastWrapper>
42
+ <ToastComponent variant='success' message='Hello' />
43
+ </ToastWrapper>
44
+ );
45
+ expect(screen.queryByText('Hello')).toBeInTheDocument();
46
+ });
47
+ it('should render a success toast', () => {
48
+ const { container } = render(
49
+ <ToastWrapper>
50
+ <ToastComponent variant='success' message='Hello' />
51
+ </ToastWrapper>
52
+ );
53
+ expect(container.querySelector('.notistack-MuiContent-success')).toBeInTheDocument();
54
+ });
55
+ it('should render a error toast', () => {
56
+ const { container } = render(
57
+ <ToastWrapper>
58
+ <ToastComponent variant='error' message='Hello' />
59
+ </ToastWrapper>
60
+ );
61
+ expect(container.querySelector('.notistack-MuiContent-error')).toBeInTheDocument();
62
+ });
63
+ it('should render a info toast', () => {
64
+ const { container } = render(
65
+ <ToastWrapper>
66
+ <ToastComponent variant='info' message='Hello' />
67
+ </ToastWrapper>
68
+ );
69
+ expect(container.querySelector('.notistack-MuiContent-info')).toBeInTheDocument();
70
+ });
71
+ it('it should render a warning toast', () => {
72
+ const { container } = render(
73
+ <ToastWrapper>
74
+ <ToastComponent variant='warning' message='Hello' />
75
+ </ToastWrapper>
76
+ );
77
+ expect(container.querySelector('.notistack-MuiContent-warning')).toBeInTheDocument();
78
+ });
79
+ it('should render a warning toast and disappear after 3 seconds', async () => {
80
+ const { container } = render(
81
+ <ToastWrapper>
82
+ <ToastComponent variant='warning' message='Hello' options={{ autoHideDuration: 3000 }}/>
83
+ </ToastWrapper>
84
+ );
85
+ expect(container.querySelector('.notistack-MuiContent-warning')).toBeInTheDocument();
86
+ await new Promise((r) => setTimeout(r, 4000));
87
+ expect(container.querySelector('.notistack-MuiContent-warning')).not.toBeInTheDocument();
88
+ });
89
+ it('should render a action buttons', async () => {
90
+
91
+ const { getByTestId } = render(
92
+ <ToastWrapper>
93
+ <ToastComponent variant='warning' message='Hello' options={{ actions: [
94
+ {
95
+ label: 'Undo',
96
+ onClick: () => alert('Undo clicked'),
97
+ },
98
+ ] }}/>
99
+ </ToastWrapper>
100
+ );
101
+ expect(getByTestId('action-button')).toBeInTheDocument();
102
+ });
103
+ it('should render close button', async () => {
104
+ const { getByTestId } = render(
105
+ <ToastWrapper>
106
+ <ToastComponent variant='warning' message='Hello' options={
107
+ {showCloseIcon: true,}}
108
+ />
109
+ </ToastWrapper>
110
+ );
111
+ expect(getByTestId('icon')).toBeInTheDocument();
112
+ });
113
+ it('should render a action buttons and close button', async () => {
114
+
115
+ const { getAllByRole } = render(
116
+ <ToastWrapper>
117
+ <ToastComponent variant='warning' message='Hello' options={
118
+ { actions: [
119
+ {
120
+ label: 'Undo',
121
+ onClick: () => alert('Undo clicked'),
122
+ },
123
+ ],
124
+ showCloseIcon: true,
125
+ }}/>
126
+ </ToastWrapper>
127
+ );
128
+ expect(getAllByRole('button')?.length).toBe(2);
129
+ });
130
+ });
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import { VariantType } from 'notistack';
3
+ import type { StoryObj, Meta } from '@storybook/react';
4
+ import { NsToastProvider, useToast, ToastOptions } from '..';
5
+
6
+ type ComponentType = typeof NsToastProvider;
7
+ type Story = StoryObj<ComponentType>;
8
+ type MetaStory = Meta<ComponentType>;
9
+
10
+ type ToastProps = {
11
+ variant?: VariantType;
12
+ message?: string;
13
+ options?: ToastOptions;
14
+ };
15
+
16
+ const ToastDecorator = (Story: React.FC) => {
17
+ return (
18
+ <NsToastProvider>
19
+ <Story />
20
+ </NsToastProvider>
21
+ )
22
+ };
23
+
24
+ const ToastComponent = (props: ToastProps) => {
25
+ const {
26
+ variant,
27
+ message = 'Toast Demo',
28
+ options = {
29
+ autoHideDuration: 2000,
30
+ },
31
+ } = props;
32
+ const { showToast } = useToast();
33
+
34
+ const toastHandler = () => {
35
+ showToast(variant, message, options);
36
+ };
37
+
38
+ return (
39
+ <div>
40
+ <button onClick={toastHandler}>
41
+ Show Toast
42
+ </button>
43
+ </div>
44
+ );
45
+ };
46
+
47
+ export default {
48
+ title: 'Components/Feedback/NsToast',
49
+ component: ToastComponent,
50
+ decorators: [ToastDecorator],
51
+ argTypes: {
52
+ variant: { control: 'select', options: ['success', 'error', 'warning', 'info'] },
53
+ message: { control: 'text' },
54
+ options: {
55
+ autoHideDuration: { control: 'number' },
56
+ persist: { control: 'boolean' },
57
+ className: { control: 'text' },
58
+ showCloseIcon: { control: 'boolean' },
59
+ actions: {
60
+ description: 'The buttons should be passed to the Toast element',
61
+ },
62
+ }
63
+ },
64
+ args: {
65
+ variant: 'error',
66
+ message: 'Toast component',
67
+ options: {
68
+ autoHideDuration: 2000,
69
+ persist: false,
70
+ showCloseIcon: false,
71
+ actions: [
72
+ {
73
+ label: 'Undo',
74
+ onClick: () => alert('Undo clicked'),
75
+ },
76
+ ],
77
+ }
78
+ },
79
+ } as MetaStory;
80
+
81
+ export const NsToast: Story = {};
@@ -0,0 +1,64 @@
1
+ import { JSX } from 'react';
2
+ import { closeSnackbar, enqueueSnackbar, VariantType } from 'notistack';
3
+ import Fade from './Fade';
4
+ import NsIcon from '../icon/NsIcon';
5
+ import NsButton from '../button/NsButton';
6
+
7
+ type ToastActionLink = {
8
+ label: string;
9
+ onClick?: () => void;
10
+ };
11
+
12
+ export type ToastOptions = {
13
+ className?: string;
14
+ autoHideDuration?: number | null;
15
+ persist?: boolean;
16
+ showCloseIcon?: boolean;
17
+ actions?: ToastActionLink[];
18
+ }
19
+
20
+
21
+ const useToast = () => {
22
+ const closeToast = (toastId?: string) => {
23
+ closeSnackbar(toastId);
24
+ };
25
+ const generateActionButtons = (showCloseIcon: boolean = false, variant: VariantType = 'success', actions?: ToastActionLink[]) => {
26
+ let actionButtons: JSX.Element[] = [];
27
+ if (actions?.length) {
28
+ actionButtons = actions.map((action) => (
29
+ <NsButton data-testid="action-button" variant="text" color={variant as any} key={action?.label} onClick={() => action.onClick?.()}>
30
+ {action.label}
31
+ </NsButton>
32
+ ));
33
+ }
34
+ if (showCloseIcon) {
35
+ actionButtons.push (
36
+ <NsButton variant="text" onClick={() => closeToast()}>
37
+ <NsIcon name="close" size="small" cursor="pointer" />
38
+ </NsButton>
39
+ );
40
+ }
41
+ return actionButtons?.length ? actionButtons : null;
42
+ };
43
+ const showToast = (variant: VariantType = 'default', message?: string, options?: ToastOptions) => {
44
+ try {
45
+ return enqueueSnackbar(message, {
46
+ variant,
47
+ ...options,
48
+ anchorOrigin: { horizontal: 'center', vertical: 'bottom' },
49
+ preventDuplicate: true,
50
+ TransitionComponent: Fade,
51
+ action: generateActionButtons(options?.showCloseIcon, variant, options?.actions),
52
+ });
53
+ } catch (err) {
54
+ console.error('CFT::UI:COMPONENTS showToast should be used inside NsToastProvider');
55
+ }
56
+ };
57
+
58
+ return {
59
+ showToast,
60
+ closeToast
61
+ };
62
+ };
63
+
64
+ export default useToast;