@mindlogic-ai/logician-ui 4.0.0-alpha.37 → 4.0.0-alpha.38
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/USAGE.md +17 -0
- package/dist/components/Toast/Toast.d.ts +7 -3
- package/dist/components/Toast/Toast.d.ts.map +1 -1
- package/dist/components/Toast/Toast.js +7 -3
- package/dist/components/Toast/Toast.js.map +1 -1
- package/dist/components/Toast/Toast.mjs +8 -4
- package/dist/components/Toast/Toast.mjs.map +1 -1
- package/dist/components/Toast/Toast.styles.d.ts +8 -0
- package/dist/components/Toast/Toast.styles.d.ts.map +1 -1
- package/dist/components/Toast/Toast.styles.js +59 -0
- package/dist/components/Toast/Toast.styles.js.map +1 -1
- package/dist/components/Toast/Toast.styles.mjs +57 -1
- package/dist/components/Toast/Toast.styles.mjs.map +1 -1
- package/dist/components/Toast/Toast.types.d.ts +25 -1
- package/dist/components/Toast/Toast.types.d.ts.map +1 -1
- package/dist/components/Toast/Toaster.d.ts.map +1 -1
- package/dist/components/Toast/Toaster.js +9 -1
- package/dist/components/Toast/Toaster.js.map +1 -1
- package/dist/components/Toast/Toaster.mjs +9 -1
- package/dist/components/Toast/Toaster.mjs.map +1 -1
- package/dist/components/Toast/useToast.d.ts +1 -1
- package/dist/components/Toast/useToast.d.ts.map +1 -1
- package/dist/components/Toast/useToast.js +9 -1
- package/dist/components/Toast/useToast.js.map +1 -1
- package/dist/components/Toast/useToast.mjs +10 -2
- package/dist/components/Toast/useToast.mjs.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/components/Toast/Toast.action.test.tsx +135 -0
- package/src/components/Toast/Toast.stories.tsx +111 -0
- package/src/components/Toast/Toast.styles.ts +60 -0
- package/src/components/Toast/Toast.tsx +16 -3
- package/src/components/Toast/Toast.types.ts +26 -1
- package/src/components/Toast/Toaster.tsx +11 -0
- package/src/components/Toast/useToast.tsx +17 -2
- package/src/guidelines/FeedbackActions.mdx +133 -0
package/USAGE.md
CHANGED
|
@@ -297,3 +297,20 @@ function App() {
|
|
|
297
297
|
Responsive Button
|
|
298
298
|
</Button>
|
|
299
299
|
```
|
|
300
|
+
|
|
301
|
+
### 5. Actions in Feedback Surfaces
|
|
302
|
+
|
|
303
|
+
- A toast takes at most one action, through the `action` prop; dismiss is the ×
|
|
304
|
+
- Never put a `<Button>` inside a toast's `description`
|
|
305
|
+
- Inside a tinted toast or banner, the action uses the status color
|
|
306
|
+
- Panes and dialogs carry buttons: ink solid primary + outline neutral
|
|
307
|
+
|
|
308
|
+
Full rules and the surface matrix: Storybook → **Guidelines / Actions in feedback surfaces** (`src/guidelines/FeedbackActions.mdx`).
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
showToast({
|
|
312
|
+
status: 'error',
|
|
313
|
+
description: '크레딧이 부족합니다.',
|
|
314
|
+
action: { label: '크레딧 구매', onClick: openCreditPurchaseModal },
|
|
315
|
+
});
|
|
316
|
+
```
|
|
@@ -2,8 +2,12 @@ import { ToastProps } from './Toast.types';
|
|
|
2
2
|
/**
|
|
3
3
|
* Toast component using Chakra UI v3 composition pattern
|
|
4
4
|
*
|
|
5
|
-
* Uses Toast.Root, Toast.Title, Toast.Description,
|
|
6
|
-
* for better composition and customization.
|
|
5
|
+
* Uses Toast.Root, Toast.Title, Toast.Description, Toast.ActionTrigger and
|
|
6
|
+
* Toast.CloseTrigger for better composition and customization.
|
|
7
|
+
*
|
|
8
|
+
* `action` renders under the description as a text action in the status
|
|
9
|
+
* color, aligned with the text column. Don't put a `<Button>` in
|
|
10
|
+
* `description` — use `action`.
|
|
7
11
|
*/
|
|
8
|
-
export declare const Toast: ({ title, description, status, onClose, ...rest }: ToastProps) => import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare const Toast: ({ title, description, status, onClose, action, ...rest }: ToastProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
13
|
//# sourceMappingURL=Toast.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toast.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C
|
|
1
|
+
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toast.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,GAAI,0DAOnB,UAAU,4CAuCZ,CAAC"}
|
|
@@ -9,10 +9,14 @@ var ToastIcon = require('./ToastIcon/ToastIcon.js');
|
|
|
9
9
|
/**
|
|
10
10
|
* Toast component using Chakra UI v3 composition pattern
|
|
11
11
|
*
|
|
12
|
-
* Uses Toast.Root, Toast.Title, Toast.Description,
|
|
13
|
-
* for better composition and customization.
|
|
12
|
+
* Uses Toast.Root, Toast.Title, Toast.Description, Toast.ActionTrigger and
|
|
13
|
+
* Toast.CloseTrigger for better composition and customization.
|
|
14
|
+
*
|
|
15
|
+
* `action` renders under the description as a text action in the status
|
|
16
|
+
* color, aligned with the text column. Don't put a `<Button>` in
|
|
17
|
+
* `description` — use `action`.
|
|
14
18
|
*/
|
|
15
|
-
const Toast = ({ title, description, status = 'success', onClose, ...rest }) => (jsxRuntime.jsxs(react.Toast.Root, { width: { md: 'sm' }, borderWidth: "1px", borderRadius: "md", boxShadow: "lg", pointerEvents: "auto", ...rest, children: [jsxRuntime.jsxs(react.Flex, { align: "center", gap: 2, children: [jsxRuntime.jsx(ToastIcon.ToastIcon, { status: status }), jsxRuntime.jsxs(react.Stack, { gap: 1, flex: 1, maxWidth: "100%", children: [title && (jsxRuntime.jsx(react.Toast.Title, { fontWeight: "bold", fontSize: "h5", children: title })), description && (jsxRuntime.jsx(react.Toast.Description, { color: "inherit", fontWeight: "semibold", children: description }))] })] }), jsxRuntime.jsx(react.Toast.CloseTrigger, { onClick: onClose, cursor: "pointer", ...Toast_styles.closeButtonStyles[status] })] }));
|
|
19
|
+
const Toast = ({ title, description, status = 'success', onClose, action, ...rest }) => (jsxRuntime.jsxs(react.Toast.Root, { width: { md: 'sm' }, borderWidth: "1px", borderRadius: "md", boxShadow: "lg", pointerEvents: "auto", ...rest, children: [jsxRuntime.jsxs(react.Flex, { align: "center", gap: 2, children: [jsxRuntime.jsx(ToastIcon.ToastIcon, { status: status }), jsxRuntime.jsxs(react.Stack, { gap: 1, flex: 1, maxWidth: "100%", children: [title && (jsxRuntime.jsx(react.Toast.Title, { fontWeight: "bold", fontSize: "h5", children: title })), description && (jsxRuntime.jsx(react.Toast.Description, { color: "inherit", fontWeight: "semibold", children: description })), action && (jsxRuntime.jsx(react.Toast.ActionTrigger, { onClick: action.onClick, ...Toast_styles.actionStyles[status], children: action.label }))] })] }), jsxRuntime.jsx(react.Toast.CloseTrigger, { onClick: onClose, cursor: "pointer", ...Toast_styles.closeButtonStyles[status] })] }));
|
|
16
20
|
|
|
17
21
|
exports.Toast = Toast;
|
|
18
22
|
//# sourceMappingURL=Toast.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.js","sources":["../../../src/components/Toast/Toast.tsx"],"sourcesContent":[null],"names":["_jsxs","ChakraToast","Flex","_jsx","ToastIcon","Stack","closeButtonStyles"],"mappings":";;;;;;;;AAMA
|
|
1
|
+
{"version":3,"file":"Toast.js","sources":["../../../src/components/Toast/Toast.tsx"],"sourcesContent":[null],"names":["_jsxs","ChakraToast","Flex","_jsx","ToastIcon","Stack","actionStyles","closeButtonStyles"],"mappings":";;;;;;;;AAMA;;;;;;;;;AASG;AACI,MAAM,KAAK,GAAG,CAAC,EACpB,KAAK,EACL,WAAW,EACX,MAAM,GAAG,SAAS,EAClB,OAAO,EACP,MAAM,EACN,GAAG,IAAI,EACI,MACXA,gBAACC,WAAW,CAAC,IAAI,EAAA,EACf,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EACnB,WAAW,EAAC,KAAK,EACjB,YAAY,EAAC,IAAI,EACjB,SAAS,EAAC,IAAI,EACd,aAAa,EAAC,MAAM,EAAA,GAChB,IAAI,EAAA,QAAA,EAAA,CAERD,gBAACE,UAAI,EAAA,EAAC,KAAK,EAAC,QAAQ,EAAC,GAAG,EAAE,CAAC,EAAA,QAAA,EAAA,CAEzBC,eAACC,mBAAS,EAAA,EAAC,MAAM,EAAE,MAAM,GAAI,EAC7BJ,eAAA,CAACK,WAAK,EAAA,EAAC,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAC,MAAM,EAAA,QAAA,EAAA,CACpC,KAAK,KACJF,eAACF,WAAW,CAAC,KAAK,EAAA,EAAC,UAAU,EAAC,MAAM,EAAC,QAAQ,EAAC,IAAI,EAAA,QAAA,EAC/C,KAAK,GACY,CACrB,EACA,WAAW,KACVE,cAAA,CAACF,WAAW,CAAC,WAAW,EAAA,EAAC,KAAK,EAAC,SAAS,EAAC,UAAU,EAAC,UAAU,EAAA,QAAA,EAC3D,WAAW,GACY,CAC3B,EACA,MAAM,KACLE,cAAA,CAACF,WAAW,CAAC,aAAa,EAAA,EACxB,OAAO,EAAE,MAAM,CAAC,OAAO,EAAA,GAClBK,yBAAY,CAAC,MAAM,CAAS,EAAA,QAAA,EAEhC,MAAM,CAAC,KAAK,EAAA,CACa,CAC7B,CAAA,EAAA,CACK,CAAA,EAAA,CACH,EACPH,cAAA,CAACF,WAAW,CAAC,YAAY,IACvB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAC,SAAS,EAAA,GACXM,8BAAiB,CAAC,MAAM,CAAS,EAAA,CACtC,CAAA,EAAA,CACe;;;;"}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { Toast as Toast$1, Flex, Stack } from '@chakra-ui/react';
|
|
4
|
-
import { closeButtonStyles } from './Toast.styles.mjs';
|
|
4
|
+
import { actionStyles, closeButtonStyles } from './Toast.styles.mjs';
|
|
5
5
|
import { ToastIcon } from './ToastIcon/ToastIcon.mjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Toast component using Chakra UI v3 composition pattern
|
|
9
9
|
*
|
|
10
|
-
* Uses Toast.Root, Toast.Title, Toast.Description,
|
|
11
|
-
* for better composition and customization.
|
|
10
|
+
* Uses Toast.Root, Toast.Title, Toast.Description, Toast.ActionTrigger and
|
|
11
|
+
* Toast.CloseTrigger for better composition and customization.
|
|
12
|
+
*
|
|
13
|
+
* `action` renders under the description as a text action in the status
|
|
14
|
+
* color, aligned with the text column. Don't put a `<Button>` in
|
|
15
|
+
* `description` — use `action`.
|
|
12
16
|
*/
|
|
13
|
-
const Toast = ({ title, description, status = 'success', onClose, ...rest }) => (jsxs(Toast$1.Root, { width: { md: 'sm' }, borderWidth: "1px", borderRadius: "md", boxShadow: "lg", pointerEvents: "auto", ...rest, children: [jsxs(Flex, { align: "center", gap: 2, children: [jsx(ToastIcon, { status: status }), jsxs(Stack, { gap: 1, flex: 1, maxWidth: "100%", children: [title && (jsx(Toast$1.Title, { fontWeight: "bold", fontSize: "h5", children: title })), description && (jsx(Toast$1.Description, { color: "inherit", fontWeight: "semibold", children: description }))] })] }), jsx(Toast$1.CloseTrigger, { onClick: onClose, cursor: "pointer", ...closeButtonStyles[status] })] }));
|
|
17
|
+
const Toast = ({ title, description, status = 'success', onClose, action, ...rest }) => (jsxs(Toast$1.Root, { width: { md: 'sm' }, borderWidth: "1px", borderRadius: "md", boxShadow: "lg", pointerEvents: "auto", ...rest, children: [jsxs(Flex, { align: "center", gap: 2, children: [jsx(ToastIcon, { status: status }), jsxs(Stack, { gap: 1, flex: 1, maxWidth: "100%", children: [title && (jsx(Toast$1.Title, { fontWeight: "bold", fontSize: "h5", children: title })), description && (jsx(Toast$1.Description, { color: "inherit", fontWeight: "semibold", children: description })), action && (jsx(Toast$1.ActionTrigger, { onClick: action.onClick, ...actionStyles[status], children: action.label }))] })] }), jsx(Toast$1.CloseTrigger, { onClick: onClose, cursor: "pointer", ...closeButtonStyles[status] })] }));
|
|
14
18
|
|
|
15
19
|
export { Toast };
|
|
16
20
|
//# sourceMappingURL=Toast.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.mjs","sources":["../../../src/components/Toast/Toast.tsx"],"sourcesContent":[null],"names":["_jsxs","ChakraToast","_jsx"],"mappings":";;;;;;AAMA
|
|
1
|
+
{"version":3,"file":"Toast.mjs","sources":["../../../src/components/Toast/Toast.tsx"],"sourcesContent":[null],"names":["_jsxs","ChakraToast","_jsx"],"mappings":";;;;;;AAMA;;;;;;;;;AASG;AACI,MAAM,KAAK,GAAG,CAAC,EACpB,KAAK,EACL,WAAW,EACX,MAAM,GAAG,SAAS,EAClB,OAAO,EACP,MAAM,EACN,GAAG,IAAI,EACI,MACXA,KAACC,OAAW,CAAC,IAAI,EAAA,EACf,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EACnB,WAAW,EAAC,KAAK,EACjB,YAAY,EAAC,IAAI,EACjB,SAAS,EAAC,IAAI,EACd,aAAa,EAAC,MAAM,EAAA,GAChB,IAAI,EAAA,QAAA,EAAA,CAERD,KAAC,IAAI,EAAA,EAAC,KAAK,EAAC,QAAQ,EAAC,GAAG,EAAE,CAAC,EAAA,QAAA,EAAA,CAEzBE,IAAC,SAAS,EAAA,EAAC,MAAM,EAAE,MAAM,GAAI,EAC7BF,IAAA,CAAC,KAAK,EAAA,EAAC,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAC,MAAM,EAAA,QAAA,EAAA,CACpC,KAAK,KACJE,IAACD,OAAW,CAAC,KAAK,EAAA,EAAC,UAAU,EAAC,MAAM,EAAC,QAAQ,EAAC,IAAI,EAAA,QAAA,EAC/C,KAAK,GACY,CACrB,EACA,WAAW,KACVC,GAAA,CAACD,OAAW,CAAC,WAAW,EAAA,EAAC,KAAK,EAAC,SAAS,EAAC,UAAU,EAAC,UAAU,EAAA,QAAA,EAC3D,WAAW,GACY,CAC3B,EACA,MAAM,KACLC,GAAA,CAACD,OAAW,CAAC,aAAa,EAAA,EACxB,OAAO,EAAE,MAAM,CAAC,OAAO,EAAA,GAClB,YAAY,CAAC,MAAM,CAAS,EAAA,QAAA,EAEhC,MAAM,CAAC,KAAK,EAAA,CACa,CAC7B,CAAA,EAAA,CACK,CAAA,EAAA,CACH,EACPC,GAAA,CAACD,OAAW,CAAC,YAAY,IACvB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAC,SAAS,EAAA,GACX,iBAAiB,CAAC,MAAM,CAAS,EAAA,CACtC,CAAA,EAAA,CACe;;;;"}
|
|
@@ -13,4 +13,12 @@ export declare const toastStyles: Record<ToastStatus, BoxProps>;
|
|
|
13
13
|
* Uses BoxProps for flexibility with Toast.CloseTrigger
|
|
14
14
|
*/
|
|
15
15
|
export declare const closeButtonStyles: Record<ToastStatus, BoxProps>;
|
|
16
|
+
export declare const actionStyles: Record<ToastStatus, BoxProps>;
|
|
17
|
+
/** Default duration (ms) of a toast without an action, when none is given */
|
|
18
|
+
export declare const TOAST_DEFAULT_DURATION = 5000;
|
|
19
|
+
/**
|
|
20
|
+
* Default duration (ms) of a toast that carries an action, when none is given.
|
|
21
|
+
* The reader needs time to read the message *and* decide to act.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TOAST_ACTION_DURATION = 8000;
|
|
16
24
|
//# sourceMappingURL=Toast.styles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.styles.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toast.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,CAqBrD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,CAyB3D,CAAC"}
|
|
1
|
+
{"version":3,"file":"Toast.styles.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toast.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,CAqBrD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,CAyB3D,CAAC;AA0BF,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,CAyBtD,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC"}
|
|
@@ -60,7 +60,66 @@ const closeButtonStyles = {
|
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Action styles for each toast status.
|
|
65
|
+
*
|
|
66
|
+
* A toast's action is a text action in the toast's own text color — the same
|
|
67
|
+
* palette as `toastStyles[status].color` — with the hover tint of the close ×.
|
|
68
|
+
* No border and no fill: a neutral or outlined button inside a tinted toast
|
|
69
|
+
* reads as a separate card dropped on top of it.
|
|
70
|
+
*
|
|
71
|
+
* `ml: -2` cancels the horizontal padding so the label lines up with the
|
|
72
|
+
* description text above it.
|
|
73
|
+
*/
|
|
74
|
+
const actionBase = {
|
|
75
|
+
fontWeight: 'bold',
|
|
76
|
+
px: 2,
|
|
77
|
+
py: 1,
|
|
78
|
+
ml: -2,
|
|
79
|
+
height: 'auto',
|
|
80
|
+
alignSelf: 'flex-start',
|
|
81
|
+
borderRadius: 'sm',
|
|
82
|
+
borderWidth: 0,
|
|
83
|
+
bg: 'transparent',
|
|
84
|
+
cursor: 'pointer',
|
|
85
|
+
};
|
|
86
|
+
const actionStyles = {
|
|
87
|
+
info: {
|
|
88
|
+
...actionBase,
|
|
89
|
+
color: 'primary.dark',
|
|
90
|
+
_hover: { bg: 'primary.lighter' },
|
|
91
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'primary.dark' },
|
|
92
|
+
},
|
|
93
|
+
warning: {
|
|
94
|
+
...actionBase,
|
|
95
|
+
color: 'warning.dark',
|
|
96
|
+
_hover: { bg: 'warning.lighter' },
|
|
97
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'warning.dark' },
|
|
98
|
+
},
|
|
99
|
+
success: {
|
|
100
|
+
...actionBase,
|
|
101
|
+
color: 'success.dark',
|
|
102
|
+
_hover: { bg: 'success.lighter' },
|
|
103
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'success.dark' },
|
|
104
|
+
},
|
|
105
|
+
error: {
|
|
106
|
+
...actionBase,
|
|
107
|
+
color: 'danger.dark',
|
|
108
|
+
_hover: { bg: 'danger.lighter' },
|
|
109
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'danger.dark' },
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
/** Default duration (ms) of a toast without an action, when none is given */
|
|
113
|
+
const TOAST_DEFAULT_DURATION = 5000;
|
|
114
|
+
/**
|
|
115
|
+
* Default duration (ms) of a toast that carries an action, when none is given.
|
|
116
|
+
* The reader needs time to read the message *and* decide to act.
|
|
117
|
+
*/
|
|
118
|
+
const TOAST_ACTION_DURATION = 8000;
|
|
63
119
|
|
|
120
|
+
exports.TOAST_ACTION_DURATION = TOAST_ACTION_DURATION;
|
|
121
|
+
exports.TOAST_DEFAULT_DURATION = TOAST_DEFAULT_DURATION;
|
|
122
|
+
exports.actionStyles = actionStyles;
|
|
64
123
|
exports.closeButtonStyles = closeButtonStyles;
|
|
65
124
|
exports.toastStyles = toastStyles;
|
|
66
125
|
//# sourceMappingURL=Toast.styles.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.styles.js","sources":["../../../src/components/Toast/Toast.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAIA;;;;;AAKG;AACI,MAAM,WAAW,GAAkC;AACxD,IAAA,IAAI,EAAE;QACJ,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,iBAAiB;AAC/B,KAAA;AACD,IAAA,OAAO,EAAE;QACP,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,iBAAiB;AAC/B,KAAA;AACD,IAAA,OAAO,EAAE;QACP,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,iBAAiB;AAC/B,KAAA;AACD,IAAA,KAAK,EAAE;QACL,EAAE,EAAE,mBAAmB;QACvB,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,gBAAgB;AAC9B,KAAA;;AAGH;;;;AAIG;AACI,MAAM,iBAAiB,GAAkC;AAC9D,IAAA,IAAI,EAAE;AACJ,QAAA,KAAK,EAAE,gBAAgB;AACvB,QAAA,MAAM,EAAE;AACN,YAAA,EAAE,EAAE,iBAAiB;AACtB,SAAA;AACF,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,KAAK,EAAE,gBAAgB;AACvB,QAAA,MAAM,EAAE;AACN,YAAA,EAAE,EAAE,iBAAiB;AACtB,SAAA;AACF,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,KAAK,EAAE,gBAAgB;AACvB,QAAA,MAAM,EAAE;AACN,YAAA,EAAE,EAAE,iBAAiB;AACtB,SAAA;AACF,KAAA;AACD,IAAA,KAAK,EAAE;AACL,QAAA,KAAK,EAAE,eAAe;AACtB,QAAA,MAAM,EAAE;AACN,YAAA,EAAE,EAAE,gBAAgB;AACrB,SAAA;AACF,KAAA
|
|
1
|
+
{"version":3,"file":"Toast.styles.js","sources":["../../../src/components/Toast/Toast.styles.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -58,6 +58,62 @@ const closeButtonStyles = {
|
|
|
58
58
|
},
|
|
59
59
|
},
|
|
60
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Action styles for each toast status.
|
|
63
|
+
*
|
|
64
|
+
* A toast's action is a text action in the toast's own text color — the same
|
|
65
|
+
* palette as `toastStyles[status].color` — with the hover tint of the close ×.
|
|
66
|
+
* No border and no fill: a neutral or outlined button inside a tinted toast
|
|
67
|
+
* reads as a separate card dropped on top of it.
|
|
68
|
+
*
|
|
69
|
+
* `ml: -2` cancels the horizontal padding so the label lines up with the
|
|
70
|
+
* description text above it.
|
|
71
|
+
*/
|
|
72
|
+
const actionBase = {
|
|
73
|
+
fontWeight: 'bold',
|
|
74
|
+
px: 2,
|
|
75
|
+
py: 1,
|
|
76
|
+
ml: -2,
|
|
77
|
+
height: 'auto',
|
|
78
|
+
alignSelf: 'flex-start',
|
|
79
|
+
borderRadius: 'sm',
|
|
80
|
+
borderWidth: 0,
|
|
81
|
+
bg: 'transparent',
|
|
82
|
+
cursor: 'pointer',
|
|
83
|
+
};
|
|
84
|
+
const actionStyles = {
|
|
85
|
+
info: {
|
|
86
|
+
...actionBase,
|
|
87
|
+
color: 'primary.dark',
|
|
88
|
+
_hover: { bg: 'primary.lighter' },
|
|
89
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'primary.dark' },
|
|
90
|
+
},
|
|
91
|
+
warning: {
|
|
92
|
+
...actionBase,
|
|
93
|
+
color: 'warning.dark',
|
|
94
|
+
_hover: { bg: 'warning.lighter' },
|
|
95
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'warning.dark' },
|
|
96
|
+
},
|
|
97
|
+
success: {
|
|
98
|
+
...actionBase,
|
|
99
|
+
color: 'success.dark',
|
|
100
|
+
_hover: { bg: 'success.lighter' },
|
|
101
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'success.dark' },
|
|
102
|
+
},
|
|
103
|
+
error: {
|
|
104
|
+
...actionBase,
|
|
105
|
+
color: 'danger.dark',
|
|
106
|
+
_hover: { bg: 'danger.lighter' },
|
|
107
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'danger.dark' },
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
/** Default duration (ms) of a toast without an action, when none is given */
|
|
111
|
+
const TOAST_DEFAULT_DURATION = 5000;
|
|
112
|
+
/**
|
|
113
|
+
* Default duration (ms) of a toast that carries an action, when none is given.
|
|
114
|
+
* The reader needs time to read the message *and* decide to act.
|
|
115
|
+
*/
|
|
116
|
+
const TOAST_ACTION_DURATION = 8000;
|
|
61
117
|
|
|
62
|
-
export { closeButtonStyles, toastStyles };
|
|
118
|
+
export { TOAST_ACTION_DURATION, TOAST_DEFAULT_DURATION, actionStyles, closeButtonStyles, toastStyles };
|
|
63
119
|
//# sourceMappingURL=Toast.styles.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.styles.mjs","sources":["../../../src/components/Toast/Toast.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";AAIA;;;;;AAKG;AACI,MAAM,WAAW,GAAkC;AACxD,IAAA,IAAI,EAAE;QACJ,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,iBAAiB;AAC/B,KAAA;AACD,IAAA,OAAO,EAAE;QACP,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,iBAAiB;AAC/B,KAAA;AACD,IAAA,OAAO,EAAE;QACP,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,iBAAiB;AAC/B,KAAA;AACD,IAAA,KAAK,EAAE;QACL,EAAE,EAAE,mBAAmB;QACvB,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,gBAAgB;AAC9B,KAAA;;AAGH;;;;AAIG;AACI,MAAM,iBAAiB,GAAkC;AAC9D,IAAA,IAAI,EAAE;AACJ,QAAA,KAAK,EAAE,gBAAgB;AACvB,QAAA,MAAM,EAAE;AACN,YAAA,EAAE,EAAE,iBAAiB;AACtB,SAAA;AACF,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,KAAK,EAAE,gBAAgB;AACvB,QAAA,MAAM,EAAE;AACN,YAAA,EAAE,EAAE,iBAAiB;AACtB,SAAA;AACF,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,KAAK,EAAE,gBAAgB;AACvB,QAAA,MAAM,EAAE;AACN,YAAA,EAAE,EAAE,iBAAiB;AACtB,SAAA;AACF,KAAA;AACD,IAAA,KAAK,EAAE;AACL,QAAA,KAAK,EAAE,eAAe;AACtB,QAAA,MAAM,EAAE;AACN,YAAA,EAAE,EAAE,gBAAgB;AACrB,SAAA;AACF,KAAA
|
|
1
|
+
{"version":3,"file":"Toast.styles.mjs","sources":["../../../src/components/Toast/Toast.styles.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,20 @@ import { BoxProps, ToastOptions, ToastRootProps, ToastStatusChangeDetails } from
|
|
|
5
5
|
* Subset of Zag.js Type, excluding 'loading' for UI consistency
|
|
6
6
|
*/
|
|
7
7
|
export type ToastStatus = 'info' | 'warning' | 'success' | 'error';
|
|
8
|
+
/**
|
|
9
|
+
* The single action a toast can carry.
|
|
10
|
+
*
|
|
11
|
+
* Rendered as a text action in the toast's status color, under the
|
|
12
|
+
* description. Clicking it calls `onClick` and dismisses the toast.
|
|
13
|
+
* One action per toast — dismiss is the ×, never a second action.
|
|
14
|
+
* See the "Guidelines/Actions in feedback surfaces" page in Storybook.
|
|
15
|
+
*/
|
|
16
|
+
export interface ToastAction {
|
|
17
|
+
/** Action label — keep it to a short verb phrase ("크레딧 구매", "Undo") */
|
|
18
|
+
label: ReactNode;
|
|
19
|
+
/** Called when the action is clicked; the toast is dismissed afterwards */
|
|
20
|
+
onClick: () => void;
|
|
21
|
+
}
|
|
8
22
|
/**
|
|
9
23
|
* Toast component props
|
|
10
24
|
* Uses ToastRootProps as base and overrides title for ReactNode support
|
|
@@ -18,14 +32,22 @@ export interface ToastProps extends Omit<ToastRootProps, 'title'> {
|
|
|
18
32
|
status?: ToastStatus;
|
|
19
33
|
/** Custom close handler */
|
|
20
34
|
onClose?: () => void;
|
|
35
|
+
/** One action, rendered as a text action in the status color */
|
|
36
|
+
action?: ToastAction;
|
|
21
37
|
}
|
|
22
38
|
/**
|
|
23
39
|
* Options for useToast hook
|
|
24
40
|
* Extends Chakra's ToastOptions with custom properties
|
|
25
41
|
*/
|
|
26
|
-
export interface UseToastOptions extends ToastOptions {
|
|
42
|
+
export interface UseToastOptions extends Omit<ToastOptions, 'action'> {
|
|
27
43
|
/** Toast status - restricted to common statuses */
|
|
28
44
|
status?: ToastStatus;
|
|
45
|
+
/**
|
|
46
|
+
* One action, rendered as a text action in the status color.
|
|
47
|
+
* Unlike Chakra's `action`, the label accepts any ReactNode.
|
|
48
|
+
* When set and `duration` is not given, the toast stays for 8s instead of 5s.
|
|
49
|
+
*/
|
|
50
|
+
action?: ToastAction;
|
|
29
51
|
/** Custom styles to apply to the toast */
|
|
30
52
|
styles?: BoxProps;
|
|
31
53
|
}
|
|
@@ -38,6 +60,8 @@ export interface ToasterCreateOptions extends ToastOptions {
|
|
|
38
60
|
meta?: {
|
|
39
61
|
/** Custom close handler for the toast */
|
|
40
62
|
onClose?: () => void;
|
|
63
|
+
/** Toast action (ReactNode label); takes precedence over Chakra's `action` */
|
|
64
|
+
action?: ToastAction;
|
|
41
65
|
/** Custom styles to merge with default toast styles */
|
|
42
66
|
styles?: BoxProps;
|
|
43
67
|
/** Allow additional custom metadata */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.types.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toast.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,wBAAwB,EACzB,MAAM,kBAAkB,CAAC;AAE1B;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE;;;GAGG;AACH,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC;IAC/D,uDAAuD;IACvD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gCAAgC;IAChC,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,wDAAwD;IACxD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,YAAY;
|
|
1
|
+
{"version":3,"file":"Toast.types.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toast.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,wBAAwB,EACzB,MAAM,kBAAkB,CAAC;AAE1B;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,KAAK,EAAE,SAAS,CAAC;IACjB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC;IAC/D,uDAAuD;IACvD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gCAAgC;IAChC,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,wDAAwD;IACxD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,gEAAgE;IAChE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC;IACnE,mDAAmD;IACnD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,mEAAmE;IACnE,IAAI,CAAC,EAAE;QACL,yCAAyC;QACzC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QACrB,8EAA8E;QAC9E,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,uDAAuD;QACvD,MAAM,CAAC,EAAE,QAAQ,CAAC;QAClB,uCAAuC;QACvC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AAED;;GAEG;AACH,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,wBAAwB,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toaster.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toaster.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,EAGpB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"Toaster.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toaster.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,EAGpB,MAAM,kBAAkB,CAAC;AAM1B;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,OAAO,EAAE,mBAGpB,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,OAAO,+CAoCnB,CAAC"}
|
|
@@ -50,7 +50,15 @@ const Toaster = () => (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(r
|
|
|
50
50
|
// Extract custom data from meta
|
|
51
51
|
const customStyles = toast.meta?.styles || {};
|
|
52
52
|
const onClose = toast.meta?.onClose;
|
|
53
|
-
|
|
53
|
+
// `useToast` passes the action through meta so its label can be a
|
|
54
|
+
// ReactNode. A plain `toaster.create({ action })` (Chakra's own option)
|
|
55
|
+
// also works: Toast.ActionTrigger already calls `toast.action.onClick`
|
|
56
|
+
// itself, so the handler passed down here must not call it again.
|
|
57
|
+
const action = toast.meta?.action ??
|
|
58
|
+
(toast.action
|
|
59
|
+
? { label: toast.action.label, onClick: () => { } }
|
|
60
|
+
: undefined);
|
|
61
|
+
return (jsxRuntime.jsx(Toast.Toast, { title: toast.title, description: toast.description, status: toast.type, onClose: onClose, action: action, ...Toast_styles.toastStyles[toast.type], ...customStyles }));
|
|
54
62
|
} }) }));
|
|
55
63
|
|
|
56
64
|
exports.Toaster = Toaster;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toaster.js","sources":["../../../src/components/Toast/Toaster.tsx"],"sourcesContent":[null],"names":["createToaster","_jsx","Portal","ChakraToaster","Toast","toastStyles"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"Toaster.js","sources":["../../../src/components/Toast/Toaster.tsx"],"sourcesContent":[null],"names":["createToaster","_jsx","Portal","ChakraToaster","Toast","toastStyles"],"mappings":";;;;;;;;AAWA;;;;;;;;;;;;AAYG;AACI,MAAM,OAAO,GAAwBA,mBAAa,CAAC;AACxD,IAAA,SAAS,EAAE,KAAK;AAChB,IAAA,eAAe,EAAE,IAAI;AACtB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACI,MAAM,OAAO,GAAG,OACrBC,cAAA,CAACC,YAAM,cACLD,cAAA,CAACE,aAAa,EAAA,EACZ,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAC5B,aAAa,EAAC,MAAM,EACpB,GAAG,EAAE,CAAC,YAEL,CAAC,KAAK,KAAI;;YAET,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE;AAC7C,YAAA,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,OAAO;;;;;AAKnC,YAAA,MAAM,MAAM,GACV,KAAK,CAAC,IAAI,EAAE,MAAM;iBACjB,KAAK,CAAC;AACL,sBAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAK,EAAE,CAAC;sBAC9C,SAAS,CAAC;AAEhB,YAAA,QACEF,cAAA,CAACG,WAAK,IACJ,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,WAAW,EAAE,KAAK,CAAC,WAAW,EAC9B,MAAM,EAAE,KAAK,CAAC,IAAI,EAClB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EAAA,GACVC,wBAAW,CAAC,KAAK,CAAC,IAAgC,CAAC,KACnD,YAAY,EAAA,CAChB;QAEN,CAAC,EAAA,CACa,EAAA,CACT;;;;;"}
|
|
@@ -48,7 +48,15 @@ const Toaster = () => (jsx(Portal, { children: jsx(Toaster$1, { toaster: toaster
|
|
|
48
48
|
// Extract custom data from meta
|
|
49
49
|
const customStyles = toast.meta?.styles || {};
|
|
50
50
|
const onClose = toast.meta?.onClose;
|
|
51
|
-
|
|
51
|
+
// `useToast` passes the action through meta so its label can be a
|
|
52
|
+
// ReactNode. A plain `toaster.create({ action })` (Chakra's own option)
|
|
53
|
+
// also works: Toast.ActionTrigger already calls `toast.action.onClick`
|
|
54
|
+
// itself, so the handler passed down here must not call it again.
|
|
55
|
+
const action = toast.meta?.action ??
|
|
56
|
+
(toast.action
|
|
57
|
+
? { label: toast.action.label, onClick: () => { } }
|
|
58
|
+
: undefined);
|
|
59
|
+
return (jsx(Toast, { title: toast.title, description: toast.description, status: toast.type, onClose: onClose, action: action, ...toastStyles[toast.type], ...customStyles }));
|
|
52
60
|
} }) }));
|
|
53
61
|
|
|
54
62
|
export { Toaster, toaster };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toaster.mjs","sources":["../../../src/components/Toast/Toaster.tsx"],"sourcesContent":[null],"names":["_jsx","ChakraToaster"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"Toaster.mjs","sources":["../../../src/components/Toast/Toaster.tsx"],"sourcesContent":[null],"names":["_jsx","ChakraToaster"],"mappings":";;;;;;AAWA;;;;;;;;;;;;AAYG;AACI,MAAM,OAAO,GAAwB,aAAa,CAAC;AACxD,IAAA,SAAS,EAAE,KAAK;AAChB,IAAA,eAAe,EAAE,IAAI;AACtB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACI,MAAM,OAAO,GAAG,OACrBA,GAAA,CAAC,MAAM,cACLA,GAAA,CAACC,SAAa,EAAA,EACZ,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAC5B,aAAa,EAAC,MAAM,EACpB,GAAG,EAAE,CAAC,YAEL,CAAC,KAAK,KAAI;;YAET,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE;AAC7C,YAAA,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,OAAO;;;;;AAKnC,YAAA,MAAM,MAAM,GACV,KAAK,CAAC,IAAI,EAAE,MAAM;iBACjB,KAAK,CAAC;AACL,sBAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAK,EAAE,CAAC;sBAC9C,SAAS,CAAC;AAEhB,YAAA,QACED,GAAA,CAAC,KAAK,IACJ,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,WAAW,EAAE,KAAK,CAAC,WAAW,EAC9B,MAAM,EAAE,KAAK,CAAC,IAAI,EAClB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EAAA,GACV,WAAW,CAAC,KAAK,CAAC,IAAgC,CAAC,KACnD,YAAY,EAAA,CAChB;QAEN,CAAC,EAAA,CACa,EAAA,CACT;;;;"}
|
|
@@ -3,7 +3,7 @@ import { UseToastOptions } from './Toast.types';
|
|
|
3
3
|
* Custom hook for managing toast notifications
|
|
4
4
|
* @returns Toast management functions
|
|
5
5
|
*/
|
|
6
|
-
export declare const useToast: () => (({ status, title, description, styles: stylesProp, duration, ...rest }: UseToastOptions) => string) & {
|
|
6
|
+
export declare const useToast: () => (({ status, title, description, styles: stylesProp, duration: durationProp, action, ...rest }: UseToastOptions) => string) & {
|
|
7
7
|
dismiss: (id?: string) => void;
|
|
8
8
|
dismissAll: () => void;
|
|
9
9
|
update: (id: string, data: Partial<import("@zag-js/toast").Props<import("react").ReactNode>>) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useToast.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/useToast.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useToast.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/useToast.tsx"],"names":[],"mappings":"AAOA,OAAO,EAGL,eAAe,EAChB,MAAM,eAAe,CAAC;AAKvB;;;GAGG;AACH,eAAO,MAAM,QAAQ,uGAWhB,eAAe;;;;CAgEnB,CAAC"}
|
|
@@ -12,7 +12,14 @@ const MAX_TOASTS = 3;
|
|
|
12
12
|
*/
|
|
13
13
|
const useToast = () => {
|
|
14
14
|
const activeToasts = React.useRef([]); // Track active toast IDs
|
|
15
|
-
const showToast = ({ status = 'success', title, description, styles: stylesProp, duration
|
|
15
|
+
const showToast = ({ status = 'success', title, description, styles: stylesProp, duration: durationProp, action, ...rest }) => {
|
|
16
|
+
// A toast with an action stays longer, unless the caller chose a duration
|
|
17
|
+
// (including `null` / `Infinity` for a persistent toast).
|
|
18
|
+
const duration = durationProp !== undefined
|
|
19
|
+
? durationProp
|
|
20
|
+
: action
|
|
21
|
+
? Toast_styles.TOAST_ACTION_DURATION
|
|
22
|
+
: Toast_styles.TOAST_DEFAULT_DURATION;
|
|
16
23
|
// NOTE: placement is not supported - must be configured in Toaster.tsx createToaster()
|
|
17
24
|
const styles = {
|
|
18
25
|
...Toast_styles.toastStyles[status],
|
|
@@ -35,6 +42,7 @@ const useToast = () => {
|
|
|
35
42
|
meta: {
|
|
36
43
|
// Pass custom data through meta
|
|
37
44
|
styles, // for custom toast style
|
|
45
|
+
action, // ReactNode label, so it travels in meta rather than `action`
|
|
38
46
|
onClose: () => Toaster.toaster.dismiss(toastId), // Custom close handler
|
|
39
47
|
},
|
|
40
48
|
onStatusChange: (details) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useToast.js","sources":["../../../src/components/Toast/useToast.tsx"],"sourcesContent":[null],"names":["useRef","toastStyles","toaster"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"useToast.js","sources":["../../../src/components/Toast/useToast.tsx"],"sourcesContent":[null],"names":["useRef","TOAST_ACTION_DURATION","TOAST_DEFAULT_DURATION","toastStyles","toaster"],"mappings":";;;;;;;AAcA,MAAM,UAAU,GAAG,CAAC;AAEpB;;;AAGG;AACI,MAAM,QAAQ,GAAG,MAAK;IAC3B,MAAM,YAAY,GAAGA,YAAM,CAAW,EAAE,CAAC,CAAC;IAE1C,MAAM,SAAS,GAAG,CAAC,EACjB,MAAM,GAAG,SAAS,EAClB,KAAK,EACL,WAAW,EACX,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,YAAY,EACtB,MAAM,EACN,GAAG,IAAI,EACS,KAAI;;;AAGpB,QAAA,MAAM,QAAQ,GACZ,YAAY,KAAK;AACf,cAAE;AACF,cAAE;AACA,kBAAEC;kBACAC,mCAAsB;;AAG9B,QAAA,MAAM,MAAM,GAAG;YACb,GAAGC,wBAAW,CAAC,MAAM,CAAC;AACtB,YAAA,GAAG,UAAU;SACd;;QAGD,IAAI,YAAY,CAAC,OAAO,CAAC,MAAM,IAAI,UAAU,EAAE;AAC7C,YAAA,MAAM,CAAC,aAAa,CAAC,GAAG,YAAY,CAAC,OAAO;YAC5C,IAAI,aAAa,EAAE;AACjB,gBAAAC,eAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;AAC/B,gBAAA,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC/B;QACF;;AAGA,QAAA,MAAM,OAAO,GAAGA,eAAO,CAAC,MAAM,CAAC;YAC7B,QAAQ;YACR,KAAK;YACL,WAAW;YACX,IAAI,EAAE,MAAM;AACZ,YAAA,IAAI,EAAE;;AAEJ,gBAAA,MAAM;AACN,gBAAA,MAAM;gBACN,OAAO,EAAE,MAAMA,eAAO,CAAC,OAAO,CAAC,OAAO,CAAC;AACxC,aAAA;AACD,YAAA,cAAc,EAAE,CAAC,OAAiC,KAAI;AACpD,gBAAA,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW,EAAE;;AAElC,oBAAA,YAAY,CAAC,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAChD,CAAC,EAAE,KAAK,EAAE,KAAK,OAAO,CACvB;gBACH;YACF,CAAC;AACD,YAAA,GAAG,IAAI;AACgB,SAAA,CAAC;;AAG1B,QAAA,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;AAElC,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,UAAU,GAAG,MAAK;AACtB,QAAA,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,KAAKA,eAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AACzD,QAAA,YAAY,CAAC,OAAO,GAAG,EAAE;AAC3B,IAAA,CAAC;AAED,IAAA,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE;QAC9B,OAAO,EAAEA,eAAO,CAAC,OAAO;QACxB,UAAU;QACV,MAAM,EAAEA,eAAO,CAAC,MAAM;AACvB,KAAA,CAAC;AACJ;;;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useRef } from 'react';
|
|
3
|
-
import { toastStyles } from './Toast.styles.mjs';
|
|
3
|
+
import { toastStyles, TOAST_ACTION_DURATION, TOAST_DEFAULT_DURATION } from './Toast.styles.mjs';
|
|
4
4
|
import { toaster } from './Toaster.mjs';
|
|
5
5
|
|
|
6
6
|
const MAX_TOASTS = 3;
|
|
@@ -10,7 +10,14 @@ const MAX_TOASTS = 3;
|
|
|
10
10
|
*/
|
|
11
11
|
const useToast = () => {
|
|
12
12
|
const activeToasts = useRef([]); // Track active toast IDs
|
|
13
|
-
const showToast = ({ status = 'success', title, description, styles: stylesProp, duration
|
|
13
|
+
const showToast = ({ status = 'success', title, description, styles: stylesProp, duration: durationProp, action, ...rest }) => {
|
|
14
|
+
// A toast with an action stays longer, unless the caller chose a duration
|
|
15
|
+
// (including `null` / `Infinity` for a persistent toast).
|
|
16
|
+
const duration = durationProp !== undefined
|
|
17
|
+
? durationProp
|
|
18
|
+
: action
|
|
19
|
+
? TOAST_ACTION_DURATION
|
|
20
|
+
: TOAST_DEFAULT_DURATION;
|
|
14
21
|
// NOTE: placement is not supported - must be configured in Toaster.tsx createToaster()
|
|
15
22
|
const styles = {
|
|
16
23
|
...toastStyles[status],
|
|
@@ -33,6 +40,7 @@ const useToast = () => {
|
|
|
33
40
|
meta: {
|
|
34
41
|
// Pass custom data through meta
|
|
35
42
|
styles, // for custom toast style
|
|
43
|
+
action, // ReactNode label, so it travels in meta rather than `action`
|
|
36
44
|
onClose: () => toaster.dismiss(toastId), // Custom close handler
|
|
37
45
|
},
|
|
38
46
|
onStatusChange: (details) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useToast.mjs","sources":["../../../src/components/Toast/useToast.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"useToast.mjs","sources":["../../../src/components/Toast/useToast.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;AAcA,MAAM,UAAU,GAAG,CAAC;AAEpB;;;AAGG;AACI,MAAM,QAAQ,GAAG,MAAK;IAC3B,MAAM,YAAY,GAAG,MAAM,CAAW,EAAE,CAAC,CAAC;IAE1C,MAAM,SAAS,GAAG,CAAC,EACjB,MAAM,GAAG,SAAS,EAClB,KAAK,EACL,WAAW,EACX,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,YAAY,EACtB,MAAM,EACN,GAAG,IAAI,EACS,KAAI;;;AAGpB,QAAA,MAAM,QAAQ,GACZ,YAAY,KAAK;AACf,cAAE;AACF,cAAE;AACA,kBAAE;kBACA,sBAAsB;;AAG9B,QAAA,MAAM,MAAM,GAAG;YACb,GAAG,WAAW,CAAC,MAAM,CAAC;AACtB,YAAA,GAAG,UAAU;SACd;;QAGD,IAAI,YAAY,CAAC,OAAO,CAAC,MAAM,IAAI,UAAU,EAAE;AAC7C,YAAA,MAAM,CAAC,aAAa,CAAC,GAAG,YAAY,CAAC,OAAO;YAC5C,IAAI,aAAa,EAAE;AACjB,gBAAA,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;AAC/B,gBAAA,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC/B;QACF;;AAGA,QAAA,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;YAC7B,QAAQ;YACR,KAAK;YACL,WAAW;YACX,IAAI,EAAE,MAAM;AACZ,YAAA,IAAI,EAAE;;AAEJ,gBAAA,MAAM;AACN,gBAAA,MAAM;gBACN,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;AACxC,aAAA;AACD,YAAA,cAAc,EAAE,CAAC,OAAiC,KAAI;AACpD,gBAAA,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW,EAAE;;AAElC,oBAAA,YAAY,CAAC,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAChD,CAAC,EAAE,KAAK,EAAE,KAAK,OAAO,CACvB;gBACH;YACF,CAAC;AACD,YAAA,GAAG,IAAI;AACgB,SAAA,CAAC;;AAG1B,QAAA,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;AAElC,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,UAAU,GAAG,MAAK;AACtB,QAAA,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AACzD,QAAA,YAAY,CAAC,OAAO,GAAG,EAAE;AAC3B,IAAA,CAAC;AAED,IAAA,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE;QAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,UAAU;QACV,MAAM,EAAE,OAAO,CAAC,MAAM;AACvB,KAAA,CAAC;AACJ;;;;"}
|
package/dist/index.js
CHANGED
|
@@ -383,6 +383,9 @@ exports.TagCloseButton = TagCloseButton.TagCloseButton;
|
|
|
383
383
|
exports.TagLabel = TagLabel.TagLabel;
|
|
384
384
|
exports.Textarea = Textarea.Textarea;
|
|
385
385
|
exports.Toast = Toast.Toast;
|
|
386
|
+
exports.TOAST_ACTION_DURATION = Toast_styles.TOAST_ACTION_DURATION;
|
|
387
|
+
exports.TOAST_DEFAULT_DURATION = Toast_styles.TOAST_DEFAULT_DURATION;
|
|
388
|
+
exports.actionStyles = Toast_styles.actionStyles;
|
|
386
389
|
exports.closeButtonStyles = Toast_styles.closeButtonStyles;
|
|
387
390
|
exports.toastStyles = Toast_styles.toastStyles;
|
|
388
391
|
exports.Toaster = Toaster.Toaster;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.mjs
CHANGED
|
@@ -138,7 +138,7 @@ export { TagCloseButton } from './components/Tag/TagCloseButton/TagCloseButton.m
|
|
|
138
138
|
export { TagLabel } from './components/Tag/TagLabel/TagLabel.mjs';
|
|
139
139
|
export { Textarea } from './components/Textarea/Textarea.mjs';
|
|
140
140
|
export { Toast } from './components/Toast/Toast.mjs';
|
|
141
|
-
export { closeButtonStyles, toastStyles } from './components/Toast/Toast.styles.mjs';
|
|
141
|
+
export { TOAST_ACTION_DURATION, TOAST_DEFAULT_DURATION, actionStyles, closeButtonStyles, toastStyles } from './components/Toast/Toast.styles.mjs';
|
|
142
142
|
export { Toaster, toaster } from './components/Toast/Toaster.mjs';
|
|
143
143
|
export { useToast } from './components/Toast/useToast.mjs';
|
|
144
144
|
export { Tooltip } from './components/Tooltip/Tooltip.mjs';
|
package/package.json
CHANGED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ChakraProvider } from '@chakra-ui/react';
|
|
2
|
+
import {
|
|
3
|
+
act,
|
|
4
|
+
fireEvent,
|
|
5
|
+
render,
|
|
6
|
+
screen,
|
|
7
|
+
waitFor,
|
|
8
|
+
} from '@testing-library/react';
|
|
9
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
10
|
+
|
|
11
|
+
import { system } from '@/theme';
|
|
12
|
+
|
|
13
|
+
import { TOAST_ACTION_DURATION, TOAST_DEFAULT_DURATION } from './Toast.styles';
|
|
14
|
+
import type { UseToastOptions } from './Toast.types';
|
|
15
|
+
import { Toaster, toaster } from './Toaster';
|
|
16
|
+
import { useToast } from './useToast';
|
|
17
|
+
|
|
18
|
+
let showToast: ReturnType<typeof useToast>;
|
|
19
|
+
|
|
20
|
+
const Harness = () => {
|
|
21
|
+
showToast = useToast();
|
|
22
|
+
return null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const renderToaster = () =>
|
|
26
|
+
render(
|
|
27
|
+
<ChakraProvider value={system}>
|
|
28
|
+
<Toaster />
|
|
29
|
+
<Harness />
|
|
30
|
+
</ChakraProvider>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const fire = (options: UseToastOptions) => {
|
|
34
|
+
let id = '';
|
|
35
|
+
act(() => {
|
|
36
|
+
id = showToast(options);
|
|
37
|
+
});
|
|
38
|
+
return id;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
act(() => {
|
|
43
|
+
toaster.remove();
|
|
44
|
+
});
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('Toast action', () => {
|
|
49
|
+
it('renders the action as a named button under the description', async () => {
|
|
50
|
+
renderToaster();
|
|
51
|
+
fire({
|
|
52
|
+
status: 'error',
|
|
53
|
+
description: '크레딧이 부족합니다.',
|
|
54
|
+
action: { label: '크레딧 구매', onClick: () => {} },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const action = await screen.findByRole('button', { name: '크레딧 구매' });
|
|
58
|
+
expect(action).toHaveAttribute('type', 'button');
|
|
59
|
+
// Exactly one action besides the close ×.
|
|
60
|
+
expect(screen.getAllByRole('button')).toHaveLength(2);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('calls onClick once and dismisses the toast', async () => {
|
|
64
|
+
renderToaster();
|
|
65
|
+
const onClick = vi.fn();
|
|
66
|
+
fire({
|
|
67
|
+
status: 'error',
|
|
68
|
+
description: '크레딧이 부족합니다.',
|
|
69
|
+
action: { label: '크레딧 구매', onClick },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const action = await screen.findByRole('button', { name: '크레딧 구매' });
|
|
73
|
+
const root = action.closest('[data-scope="toast"][data-part="root"]');
|
|
74
|
+
expect(root).toHaveAttribute('data-state', 'open');
|
|
75
|
+
|
|
76
|
+
fireEvent.click(action);
|
|
77
|
+
|
|
78
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
79
|
+
// Dismissed: the root leaves the open state (exit animation follows).
|
|
80
|
+
await waitFor(() => expect(root).toHaveAttribute('data-state', 'closed'));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("accepts Chakra's own toaster.create({ action }) without double-calling", async () => {
|
|
84
|
+
renderToaster();
|
|
85
|
+
const onClick = vi.fn();
|
|
86
|
+
act(() => {
|
|
87
|
+
toaster.create({
|
|
88
|
+
type: 'info',
|
|
89
|
+
description: 'Exported.',
|
|
90
|
+
action: { label: 'Open', onClick },
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
fireEvent.click(await screen.findByRole('button', { name: 'Open' }));
|
|
95
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('renders no action button when none is given', async () => {
|
|
99
|
+
renderToaster();
|
|
100
|
+
fire({ status: 'success', description: 'Saved.' });
|
|
101
|
+
|
|
102
|
+
await screen.findByText('Saved.');
|
|
103
|
+
expect(screen.getAllByRole('button')).toHaveLength(1); // close × only
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('useToast duration', () => {
|
|
108
|
+
const durationOf = (options: UseToastOptions) => {
|
|
109
|
+
const create = vi.spyOn(toaster, 'create');
|
|
110
|
+
fire(options);
|
|
111
|
+
return create.mock.calls.at(-1)?.[0].duration;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
it('defaults to 5s without an action', () => {
|
|
115
|
+
renderToaster();
|
|
116
|
+
expect(durationOf({ description: 'Saved.' })).toBe(TOAST_DEFAULT_DURATION);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('defaults to 8s with an action', () => {
|
|
120
|
+
renderToaster();
|
|
121
|
+
expect(
|
|
122
|
+
durationOf({
|
|
123
|
+
description: 'Deleted.',
|
|
124
|
+
action: { label: 'Undo', onClick: () => {} },
|
|
125
|
+
})
|
|
126
|
+
).toBe(TOAST_ACTION_DURATION);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("keeps the caller's duration, including null", () => {
|
|
130
|
+
renderToaster();
|
|
131
|
+
const action = { label: 'Undo', onClick: () => {} };
|
|
132
|
+
expect(durationOf({ description: 'a', action, duration: 3000 })).toBe(3000);
|
|
133
|
+
expect(durationOf({ description: 'b', action, duration: null })).toBe(null);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -4,6 +4,7 @@ import { useState } from 'react';
|
|
|
4
4
|
|
|
5
5
|
import { Button } from '../Button';
|
|
6
6
|
import { Toast } from './Toast';
|
|
7
|
+
import type { UseToastOptions } from './Toast.types';
|
|
7
8
|
import { Toaster } from './Toaster';
|
|
8
9
|
import { useToast } from './useToast';
|
|
9
10
|
|
|
@@ -481,3 +482,113 @@ export const RapidFire: Story = {
|
|
|
481
482
|
);
|
|
482
483
|
},
|
|
483
484
|
};
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Toasts with an action.
|
|
488
|
+
*
|
|
489
|
+
* The action is a text action in the toast's own status color, under the
|
|
490
|
+
* description and aligned with it — not a neutral `<Button>` inside
|
|
491
|
+
* `description`. One action per toast; dismiss stays on the ×. A toast with
|
|
492
|
+
* an action stays for 8s unless `duration` is given.
|
|
493
|
+
*
|
|
494
|
+
* See "Guidelines/Actions in feedback surfaces".
|
|
495
|
+
*/
|
|
496
|
+
const ActionStory = ({
|
|
497
|
+
label,
|
|
498
|
+
options,
|
|
499
|
+
}: {
|
|
500
|
+
label: string;
|
|
501
|
+
/** Builds the toast options; `fired` reports a click back to the story */
|
|
502
|
+
options: (fired: (message: string) => () => void) => UseToastOptions;
|
|
503
|
+
}) => {
|
|
504
|
+
const showToast = useToast();
|
|
505
|
+
const [log, setLog] = useState<string | null>(null);
|
|
506
|
+
const fired = (message: string) => () => setLog(`onClick: ${message}`);
|
|
507
|
+
|
|
508
|
+
return (
|
|
509
|
+
<>
|
|
510
|
+
<Toaster />
|
|
511
|
+
<Stack gap={3} align="center">
|
|
512
|
+
<Button onClick={() => showToast(options(fired))}>{label}</Button>
|
|
513
|
+
{log && (
|
|
514
|
+
<Text fontSize="sm" color="fg.muted">
|
|
515
|
+
{log}
|
|
516
|
+
</Text>
|
|
517
|
+
)}
|
|
518
|
+
</Stack>
|
|
519
|
+
</>
|
|
520
|
+
);
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
export const ActionError: Story = {
|
|
524
|
+
name: 'Action / Error',
|
|
525
|
+
render: () => (
|
|
526
|
+
<ActionStory
|
|
527
|
+
label="Show error toast with action"
|
|
528
|
+
options={(fired) => ({
|
|
529
|
+
status: 'error',
|
|
530
|
+
description:
|
|
531
|
+
'크레딧이 부족해 응답을 생성하지 못했습니다. 이번 달 남은 크레딧: 0',
|
|
532
|
+
action: { label: '크레딧 구매', onClick: fired('크레딧 구매') },
|
|
533
|
+
})}
|
|
534
|
+
/>
|
|
535
|
+
),
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
export const ActionWarning: Story = {
|
|
539
|
+
name: 'Action / Warning',
|
|
540
|
+
render: () => (
|
|
541
|
+
<ActionStory
|
|
542
|
+
label="Show warning toast with action"
|
|
543
|
+
options={(fired) => ({
|
|
544
|
+
status: 'warning',
|
|
545
|
+
title: '모델 지원 종료',
|
|
546
|
+
description: '이 채팅의 모델이 지원 종료되어 기본 모델로 바뀌었습니다.',
|
|
547
|
+
action: { label: '모델 선택', onClick: fired('모델 선택') },
|
|
548
|
+
})}
|
|
549
|
+
/>
|
|
550
|
+
),
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
export const ActionInfo: Story = {
|
|
554
|
+
name: 'Action / Info',
|
|
555
|
+
render: () => (
|
|
556
|
+
<ActionStory
|
|
557
|
+
label="Show info toast with action"
|
|
558
|
+
options={(fired) => ({
|
|
559
|
+
status: 'info',
|
|
560
|
+
description: '내보내기가 끝났습니다. 파일을 다운로드할 수 있습니다.',
|
|
561
|
+
action: { label: '파일 열기', onClick: fired('파일 열기') },
|
|
562
|
+
})}
|
|
563
|
+
/>
|
|
564
|
+
),
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
export const ActionSuccess: Story = {
|
|
568
|
+
name: 'Action / Success',
|
|
569
|
+
render: () => (
|
|
570
|
+
<ActionStory
|
|
571
|
+
label="Show success toast with action"
|
|
572
|
+
options={(fired) => ({
|
|
573
|
+
status: 'success',
|
|
574
|
+
title: '업로드 완료',
|
|
575
|
+
description: '문서 3개를 프로젝트에 추가했습니다.',
|
|
576
|
+
action: { label: '프로젝트 보기', onClick: fired('프로젝트 보기') },
|
|
577
|
+
})}
|
|
578
|
+
/>
|
|
579
|
+
),
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
export const ActionShortMessage: Story = {
|
|
583
|
+
name: 'Action / Short message',
|
|
584
|
+
render: () => (
|
|
585
|
+
<ActionStory
|
|
586
|
+
label="Show short toast with undo"
|
|
587
|
+
options={(fired) => ({
|
|
588
|
+
status: 'success',
|
|
589
|
+
description: '채팅을 삭제했습니다.',
|
|
590
|
+
action: { label: '되돌리기', onClick: fired('되돌리기') },
|
|
591
|
+
})}
|
|
592
|
+
/>
|
|
593
|
+
),
|
|
594
|
+
};
|
|
@@ -62,3 +62,63 @@ export const closeButtonStyles: Record<ToastStatus, BoxProps> = {
|
|
|
62
62
|
},
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Action styles for each toast status.
|
|
68
|
+
*
|
|
69
|
+
* A toast's action is a text action in the toast's own text color — the same
|
|
70
|
+
* palette as `toastStyles[status].color` — with the hover tint of the close ×.
|
|
71
|
+
* No border and no fill: a neutral or outlined button inside a tinted toast
|
|
72
|
+
* reads as a separate card dropped on top of it.
|
|
73
|
+
*
|
|
74
|
+
* `ml: -2` cancels the horizontal padding so the label lines up with the
|
|
75
|
+
* description text above it.
|
|
76
|
+
*/
|
|
77
|
+
const actionBase: BoxProps = {
|
|
78
|
+
fontWeight: 'bold',
|
|
79
|
+
px: 2,
|
|
80
|
+
py: 1,
|
|
81
|
+
ml: -2,
|
|
82
|
+
height: 'auto',
|
|
83
|
+
alignSelf: 'flex-start',
|
|
84
|
+
borderRadius: 'sm',
|
|
85
|
+
borderWidth: 0,
|
|
86
|
+
bg: 'transparent',
|
|
87
|
+
cursor: 'pointer',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const actionStyles: Record<ToastStatus, BoxProps> = {
|
|
91
|
+
info: {
|
|
92
|
+
...actionBase,
|
|
93
|
+
color: 'primary.dark',
|
|
94
|
+
_hover: { bg: 'primary.lighter' },
|
|
95
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'primary.dark' },
|
|
96
|
+
},
|
|
97
|
+
warning: {
|
|
98
|
+
...actionBase,
|
|
99
|
+
color: 'warning.dark',
|
|
100
|
+
_hover: { bg: 'warning.lighter' },
|
|
101
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'warning.dark' },
|
|
102
|
+
},
|
|
103
|
+
success: {
|
|
104
|
+
...actionBase,
|
|
105
|
+
color: 'success.dark',
|
|
106
|
+
_hover: { bg: 'success.lighter' },
|
|
107
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'success.dark' },
|
|
108
|
+
},
|
|
109
|
+
error: {
|
|
110
|
+
...actionBase,
|
|
111
|
+
color: 'danger.dark',
|
|
112
|
+
_hover: { bg: 'danger.lighter' },
|
|
113
|
+
_focusVisible: { outline: '2px solid', outlineColor: 'danger.dark' },
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** Default duration (ms) of a toast without an action, when none is given */
|
|
118
|
+
export const TOAST_DEFAULT_DURATION = 5000;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Default duration (ms) of a toast that carries an action, when none is given.
|
|
122
|
+
* The reader needs time to read the message *and* decide to act.
|
|
123
|
+
*/
|
|
124
|
+
export const TOAST_ACTION_DURATION = 8000;
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import { Flex, Stack, Toast as ChakraToast } from '@chakra-ui/react';
|
|
2
2
|
|
|
3
|
-
import { closeButtonStyles } from './Toast.styles';
|
|
3
|
+
import { actionStyles, closeButtonStyles } from './Toast.styles';
|
|
4
4
|
import { ToastProps } from './Toast.types';
|
|
5
5
|
import { ToastIcon } from './ToastIcon/ToastIcon';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Toast component using Chakra UI v3 composition pattern
|
|
9
9
|
*
|
|
10
|
-
* Uses Toast.Root, Toast.Title, Toast.Description,
|
|
11
|
-
* for better composition and customization.
|
|
10
|
+
* Uses Toast.Root, Toast.Title, Toast.Description, Toast.ActionTrigger and
|
|
11
|
+
* Toast.CloseTrigger for better composition and customization.
|
|
12
|
+
*
|
|
13
|
+
* `action` renders under the description as a text action in the status
|
|
14
|
+
* color, aligned with the text column. Don't put a `<Button>` in
|
|
15
|
+
* `description` — use `action`.
|
|
12
16
|
*/
|
|
13
17
|
export const Toast = ({
|
|
14
18
|
title,
|
|
15
19
|
description,
|
|
16
20
|
status = 'success',
|
|
17
21
|
onClose,
|
|
22
|
+
action,
|
|
18
23
|
...rest
|
|
19
24
|
}: ToastProps) => (
|
|
20
25
|
<ChakraToast.Root
|
|
@@ -39,6 +44,14 @@ export const Toast = ({
|
|
|
39
44
|
{description}
|
|
40
45
|
</ChakraToast.Description>
|
|
41
46
|
)}
|
|
47
|
+
{action && (
|
|
48
|
+
<ChakraToast.ActionTrigger
|
|
49
|
+
onClick={action.onClick}
|
|
50
|
+
{...(actionStyles[status] as any)}
|
|
51
|
+
>
|
|
52
|
+
{action.label}
|
|
53
|
+
</ChakraToast.ActionTrigger>
|
|
54
|
+
)}
|
|
42
55
|
</Stack>
|
|
43
56
|
</Flex>
|
|
44
57
|
<ChakraToast.CloseTrigger
|
|
@@ -12,6 +12,21 @@ import {
|
|
|
12
12
|
*/
|
|
13
13
|
export type ToastStatus = 'info' | 'warning' | 'success' | 'error';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* The single action a toast can carry.
|
|
17
|
+
*
|
|
18
|
+
* Rendered as a text action in the toast's status color, under the
|
|
19
|
+
* description. Clicking it calls `onClick` and dismisses the toast.
|
|
20
|
+
* One action per toast — dismiss is the ×, never a second action.
|
|
21
|
+
* See the "Guidelines/Actions in feedback surfaces" page in Storybook.
|
|
22
|
+
*/
|
|
23
|
+
export interface ToastAction {
|
|
24
|
+
/** Action label — keep it to a short verb phrase ("크레딧 구매", "Undo") */
|
|
25
|
+
label: ReactNode;
|
|
26
|
+
/** Called when the action is clicked; the toast is dismissed afterwards */
|
|
27
|
+
onClick: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
15
30
|
/**
|
|
16
31
|
* Toast component props
|
|
17
32
|
* Uses ToastRootProps as base and overrides title for ReactNode support
|
|
@@ -25,15 +40,23 @@ export interface ToastProps extends Omit<ToastRootProps, 'title'> {
|
|
|
25
40
|
status?: ToastStatus;
|
|
26
41
|
/** Custom close handler */
|
|
27
42
|
onClose?: () => void;
|
|
43
|
+
/** One action, rendered as a text action in the status color */
|
|
44
|
+
action?: ToastAction;
|
|
28
45
|
}
|
|
29
46
|
|
|
30
47
|
/**
|
|
31
48
|
* Options for useToast hook
|
|
32
49
|
* Extends Chakra's ToastOptions with custom properties
|
|
33
50
|
*/
|
|
34
|
-
export interface UseToastOptions extends ToastOptions {
|
|
51
|
+
export interface UseToastOptions extends Omit<ToastOptions, 'action'> {
|
|
35
52
|
/** Toast status - restricted to common statuses */
|
|
36
53
|
status?: ToastStatus;
|
|
54
|
+
/**
|
|
55
|
+
* One action, rendered as a text action in the status color.
|
|
56
|
+
* Unlike Chakra's `action`, the label accepts any ReactNode.
|
|
57
|
+
* When set and `duration` is not given, the toast stays for 8s instead of 5s.
|
|
58
|
+
*/
|
|
59
|
+
action?: ToastAction;
|
|
37
60
|
/** Custom styles to apply to the toast */
|
|
38
61
|
styles?: BoxProps;
|
|
39
62
|
}
|
|
@@ -47,6 +70,8 @@ export interface ToasterCreateOptions extends ToastOptions {
|
|
|
47
70
|
meta?: {
|
|
48
71
|
/** Custom close handler for the toast */
|
|
49
72
|
onClose?: () => void;
|
|
73
|
+
/** Toast action (ReactNode label); takes precedence over Chakra's `action` */
|
|
74
|
+
action?: ToastAction;
|
|
50
75
|
/** Custom styles to merge with default toast styles */
|
|
51
76
|
styles?: BoxProps;
|
|
52
77
|
/** Allow additional custom metadata */
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
import { Toast } from './Toast';
|
|
9
9
|
import { toastStyles } from './Toast.styles';
|
|
10
|
+
import type { ToastAction } from './Toast.types';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Global toaster instance for managing toast notifications
|
|
@@ -61,6 +62,15 @@ export const Toaster = () => (
|
|
|
61
62
|
// Extract custom data from meta
|
|
62
63
|
const customStyles = toast.meta?.styles || {};
|
|
63
64
|
const onClose = toast.meta?.onClose;
|
|
65
|
+
// `useToast` passes the action through meta so its label can be a
|
|
66
|
+
// ReactNode. A plain `toaster.create({ action })` (Chakra's own option)
|
|
67
|
+
// also works: Toast.ActionTrigger already calls `toast.action.onClick`
|
|
68
|
+
// itself, so the handler passed down here must not call it again.
|
|
69
|
+
const action: ToastAction | undefined =
|
|
70
|
+
toast.meta?.action ??
|
|
71
|
+
(toast.action
|
|
72
|
+
? { label: toast.action.label, onClick: () => {} }
|
|
73
|
+
: undefined);
|
|
64
74
|
|
|
65
75
|
return (
|
|
66
76
|
<Toast
|
|
@@ -68,6 +78,7 @@ export const Toaster = () => (
|
|
|
68
78
|
description={toast.description}
|
|
69
79
|
status={toast.type}
|
|
70
80
|
onClose={onClose}
|
|
81
|
+
action={action}
|
|
71
82
|
{...toastStyles[toast.type as keyof typeof toastStyles]}
|
|
72
83
|
{...customStyles}
|
|
73
84
|
/>
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { useRef } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
TOAST_ACTION_DURATION,
|
|
5
|
+
TOAST_DEFAULT_DURATION,
|
|
6
|
+
toastStyles,
|
|
7
|
+
} from './Toast.styles';
|
|
4
8
|
import {
|
|
5
9
|
ToasterCreateOptions,
|
|
6
10
|
ToastStatusChangeDetails,
|
|
@@ -22,9 +26,19 @@ export const useToast = () => {
|
|
|
22
26
|
title,
|
|
23
27
|
description,
|
|
24
28
|
styles: stylesProp,
|
|
25
|
-
duration
|
|
29
|
+
duration: durationProp,
|
|
30
|
+
action,
|
|
26
31
|
...rest
|
|
27
32
|
}: UseToastOptions) => {
|
|
33
|
+
// A toast with an action stays longer, unless the caller chose a duration
|
|
34
|
+
// (including `null` / `Infinity` for a persistent toast).
|
|
35
|
+
const duration =
|
|
36
|
+
durationProp !== undefined
|
|
37
|
+
? durationProp
|
|
38
|
+
: action
|
|
39
|
+
? TOAST_ACTION_DURATION
|
|
40
|
+
: TOAST_DEFAULT_DURATION;
|
|
41
|
+
|
|
28
42
|
// NOTE: placement is not supported - must be configured in Toaster.tsx createToaster()
|
|
29
43
|
const styles = {
|
|
30
44
|
...toastStyles[status],
|
|
@@ -49,6 +63,7 @@ export const useToast = () => {
|
|
|
49
63
|
meta: {
|
|
50
64
|
// Pass custom data through meta
|
|
51
65
|
styles, // for custom toast style
|
|
66
|
+
action, // ReactNode label, so it travels in meta rather than `action`
|
|
52
67
|
onClose: () => toaster.dismiss(toastId), // Custom close handler
|
|
53
68
|
},
|
|
54
69
|
onStatusChange: (details: ToastStatusChangeDetails) => {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Guidelines/Actions in feedback surfaces" />
|
|
4
|
+
|
|
5
|
+
# Actions in feedback surfaces
|
|
6
|
+
|
|
7
|
+
Toasts, inline banners, panes and dialogs often need a control: "buy credits",
|
|
8
|
+
"undo", "open file". This page covers which control goes where and how it
|
|
9
|
+
should look.
|
|
10
|
+
|
|
11
|
+
The test for every case: **does the control look like it belongs to the
|
|
12
|
+
container it sits in?**
|
|
13
|
+
|
|
14
|
+
> Visual prototype with before/after and a playground:
|
|
15
|
+
> [feedback-actions-proto.apps.mindlogic.tools](https://feedback-actions-proto.apps.mindlogic.tools/)
|
|
16
|
+
> (company login).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why this exists
|
|
21
|
+
|
|
22
|
+
Until `Toast` had an `action` prop, callers who wanted a button in a toast put
|
|
23
|
+
a `<Button>` inside `description` and styled it themselves. On the credit-402
|
|
24
|
+
toast in FactChat that produced a neutral outline button (`#494949` text, ink
|
|
25
|
+
hairline) on a rose toast (`#FFE6E8` background, `#69000A` text). It carried
|
|
26
|
+
none of the toast's colors, so it read as a separate card dropped on top.
|
|
27
|
+
|
|
28
|
+
The fix is one recipe for every toast: the action uses the toast's own text
|
|
29
|
+
color, the same treatment as the close ×.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Rules
|
|
34
|
+
|
|
35
|
+
### 1. Match the container's weight
|
|
36
|
+
|
|
37
|
+
Toasts and inline banners carry **text actions**. Buttons belong to surfaces
|
|
38
|
+
that own the space: a pane, a dialog, an empty state.
|
|
39
|
+
|
|
40
|
+
### 2. The action takes the status color
|
|
41
|
+
|
|
42
|
+
Inside a tinted container the action uses **that container's text color**
|
|
43
|
+
(`danger.dark` on an error toast, `primary.dark` on an info toast). Never a
|
|
44
|
+
neutral control.
|
|
45
|
+
|
|
46
|
+
### 3. One action per toast; dismiss is ×
|
|
47
|
+
|
|
48
|
+
No 「닫기」 or 「취소」 button in a toast; the × already does that. If a
|
|
49
|
+
message needs two actions, it is not a toast; use a dialog or a pane.
|
|
50
|
+
|
|
51
|
+
### 4. Don't repeat the action in the copy
|
|
52
|
+
|
|
53
|
+
If the action says 「크레딧 구매」, the message doesn't also say "purchase
|
|
54
|
+
credits to continue". Toast copy stays **within 2 lines**.
|
|
55
|
+
|
|
56
|
+
### 5. In a pane, the fix is the primary
|
|
57
|
+
|
|
58
|
+
In a pane or empty state, the action that fixes the problem is the primary
|
|
59
|
+
(ink solid, `colorPalette="neutral" variant="solid"`). Dismiss is outline
|
|
60
|
+
neutral (`colorPalette="neutral" variant="outline"`).
|
|
61
|
+
|
|
62
|
+
### 6. Toasts with an action stay longer
|
|
63
|
+
|
|
64
|
+
The reader has to read the message and then decide to act. A toast with an
|
|
65
|
+
action defaults to **8s** instead of 5s. `useToast` does this for you when
|
|
66
|
+
`duration` is not given.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Which control goes where
|
|
71
|
+
|
|
72
|
+
| Surface | Primary action | Secondary / dismiss | Max actions | Copy |
|
|
73
|
+
| ------------------ | -------------------------------------------------- | -------------------------------- | ----------- | ------------------------------------------ |
|
|
74
|
+
| Toast | Text action in status color, via the `action` prop | × only | 1 | ≤ 2 lines, no hint that repeats the action |
|
|
75
|
+
| Inline banner | Trailing text action in status color | × if dismissible | 1 | 1 line, wraps on mobile |
|
|
76
|
+
| Pane / empty state | Ink solid (`neutral` `solid`) | Outline neutral | 2 | Title + reason + optional hint |
|
|
77
|
+
| Dialog | Ink solid, right | Outline neutral, left of primary | 2 | Title + body |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { useToast } from '@mindlogic-ai/logician-ui';
|
|
85
|
+
|
|
86
|
+
const showToast = useToast();
|
|
87
|
+
|
|
88
|
+
showToast({
|
|
89
|
+
status: 'error',
|
|
90
|
+
description: '크레딧이 부족해 응답을 생성하지 못했습니다.',
|
|
91
|
+
action: {
|
|
92
|
+
label: '크레딧 구매',
|
|
93
|
+
onClick: openCreditPurchaseModal,
|
|
94
|
+
},
|
|
95
|
+
// duration defaults to 8000 because an action is set
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- The action renders under the description, aligned with the text column, in
|
|
100
|
+
the toast's status color with the same hover tint as the ×.
|
|
101
|
+
- Clicking it calls `onClick` and dismisses the toast.
|
|
102
|
+
- `label` accepts any `ReactNode`, but keep it a short verb phrase.
|
|
103
|
+
- An explicit `duration` (including `null` for a persistent toast) always wins.
|
|
104
|
+
- Chakra's own `toaster.create({ action: { label, onClick } })` renders the
|
|
105
|
+
same way; with that route the label must be a string and the duration
|
|
106
|
+
default is Chakra's, not 8s.
|
|
107
|
+
|
|
108
|
+
### Don't: a button inside `description`
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// ✗ A neutral control dropped onto a tinted toast; it carries none of the
|
|
112
|
+
// toast's colors, adds a third line, and dismissal is left to the caller.
|
|
113
|
+
showToast({
|
|
114
|
+
status: 'error',
|
|
115
|
+
description: (
|
|
116
|
+
<>
|
|
117
|
+
크레딧이 부족합니다. 크레딧을 구매하면 계속 사용할 수 있습니다.
|
|
118
|
+
<Button colorPalette="neutral" variant="outline" onClick={buy}>
|
|
119
|
+
크레딧 구매
|
|
120
|
+
</Button>
|
|
121
|
+
</>
|
|
122
|
+
),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// ✓ One action, in the toast's color, and the copy doesn't repeat it.
|
|
126
|
+
showToast({
|
|
127
|
+
status: 'error',
|
|
128
|
+
description: '크레딧이 부족합니다.',
|
|
129
|
+
action: { label: '크레딧 구매', onClick: buy },
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
See **Components/Toast → Action / …** for each status and a short-message case.
|