@mindlogic-ai/logician-ui 4.0.0-alpha.36 → 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.
Files changed (98) hide show
  1. package/USAGE.md +17 -0
  2. package/dist/components/Form/FormSection/CollapsibleFormSection.js +1 -0
  3. package/dist/components/Form/FormSection/CollapsibleFormSection.js.map +1 -1
  4. package/dist/components/Form/FormSection/CollapsibleFormSection.mjs +1 -0
  5. package/dist/components/Form/FormSection/CollapsibleFormSection.mjs.map +1 -1
  6. package/dist/components/Toast/Toast.d.ts +7 -3
  7. package/dist/components/Toast/Toast.d.ts.map +1 -1
  8. package/dist/components/Toast/Toast.js +7 -3
  9. package/dist/components/Toast/Toast.js.map +1 -1
  10. package/dist/components/Toast/Toast.mjs +8 -4
  11. package/dist/components/Toast/Toast.mjs.map +1 -1
  12. package/dist/components/Toast/Toast.styles.d.ts +8 -0
  13. package/dist/components/Toast/Toast.styles.d.ts.map +1 -1
  14. package/dist/components/Toast/Toast.styles.js +59 -0
  15. package/dist/components/Toast/Toast.styles.js.map +1 -1
  16. package/dist/components/Toast/Toast.styles.mjs +57 -1
  17. package/dist/components/Toast/Toast.styles.mjs.map +1 -1
  18. package/dist/components/Toast/Toast.types.d.ts +25 -1
  19. package/dist/components/Toast/Toast.types.d.ts.map +1 -1
  20. package/dist/components/Toast/Toaster.d.ts.map +1 -1
  21. package/dist/components/Toast/Toaster.js +9 -1
  22. package/dist/components/Toast/Toaster.js.map +1 -1
  23. package/dist/components/Toast/Toaster.mjs +9 -1
  24. package/dist/components/Toast/Toaster.mjs.map +1 -1
  25. package/dist/components/Toast/useToast.d.ts +1 -1
  26. package/dist/components/Toast/useToast.d.ts.map +1 -1
  27. package/dist/components/Toast/useToast.js +9 -1
  28. package/dist/components/Toast/useToast.js.map +1 -1
  29. package/dist/components/Toast/useToast.mjs +10 -2
  30. package/dist/components/Toast/useToast.mjs.map +1 -1
  31. package/dist/components/Workflow/Workflow.d.ts +1 -1
  32. package/dist/components/Workflow/Workflow.d.ts.map +1 -1
  33. package/dist/components/Workflow/Workflow.js +2 -2
  34. package/dist/components/Workflow/Workflow.js.map +1 -1
  35. package/dist/components/Workflow/Workflow.mjs +2 -2
  36. package/dist/components/Workflow/Workflow.mjs.map +1 -1
  37. package/dist/components/Workflow/Workflow.types.d.ts +9 -0
  38. package/dist/components/Workflow/Workflow.types.d.ts.map +1 -1
  39. package/dist/components/Workflow/Workflow.types.js.map +1 -1
  40. package/dist/components/Workflow/Workflow.types.mjs.map +1 -1
  41. package/dist/components/Workflow/canvas/Canvas/Canvas.d.ts +6 -2
  42. package/dist/components/Workflow/canvas/Canvas/Canvas.d.ts.map +1 -1
  43. package/dist/components/Workflow/canvas/Canvas/Canvas.js +29 -23
  44. package/dist/components/Workflow/canvas/Canvas/Canvas.js.map +1 -1
  45. package/dist/components/Workflow/canvas/Canvas/Canvas.mjs +31 -25
  46. package/dist/components/Workflow/canvas/Canvas/Canvas.mjs.map +1 -1
  47. package/dist/components/Workflow/createNode.d.ts +25 -1
  48. package/dist/components/Workflow/createNode.d.ts.map +1 -1
  49. package/dist/components/Workflow/createNode.js +39 -1
  50. package/dist/components/Workflow/createNode.js.map +1 -1
  51. package/dist/components/Workflow/createNode.mjs +38 -2
  52. package/dist/components/Workflow/createNode.mjs.map +1 -1
  53. package/dist/components/Workflow/index.d.ts +2 -0
  54. package/dist/components/Workflow/index.d.ts.map +1 -1
  55. package/dist/components/Workflow/layout/autoLayout.d.ts +10 -0
  56. package/dist/components/Workflow/layout/autoLayout.d.ts.map +1 -1
  57. package/dist/components/Workflow/layout/autoLayout.js +4 -0
  58. package/dist/components/Workflow/layout/autoLayout.js.map +1 -1
  59. package/dist/components/Workflow/layout/autoLayout.mjs +1 -1
  60. package/dist/components/Workflow/layout/autoLayout.mjs.map +1 -1
  61. package/dist/components/Workflow/placement.d.ts +65 -0
  62. package/dist/components/Workflow/placement.d.ts.map +1 -0
  63. package/dist/components/Workflow/placement.js +100 -0
  64. package/dist/components/Workflow/placement.js.map +1 -0
  65. package/dist/components/Workflow/placement.mjs +96 -0
  66. package/dist/components/Workflow/placement.mjs.map +1 -0
  67. package/dist/components/Workflow/useWorkflowActions.d.ts +54 -0
  68. package/dist/components/Workflow/useWorkflowActions.d.ts.map +1 -0
  69. package/dist/components/Workflow/useWorkflowActions.js +105 -0
  70. package/dist/components/Workflow/useWorkflowActions.js.map +1 -0
  71. package/dist/components/Workflow/useWorkflowActions.mjs +103 -0
  72. package/dist/components/Workflow/useWorkflowActions.mjs.map +1 -0
  73. package/dist/index.js +5 -0
  74. package/dist/index.js.map +1 -1
  75. package/dist/index.mjs +2 -1
  76. package/dist/index.mjs.map +1 -1
  77. package/package.json +1 -1
  78. package/src/components/Toast/Toast.action.test.tsx +135 -0
  79. package/src/components/Toast/Toast.stories.tsx +111 -0
  80. package/src/components/Toast/Toast.styles.ts +60 -0
  81. package/src/components/Toast/Toast.tsx +16 -3
  82. package/src/components/Toast/Toast.types.ts +26 -1
  83. package/src/components/Toast/Toaster.tsx +11 -0
  84. package/src/components/Toast/useToast.tsx +17 -2
  85. package/src/components/Workflow/README.md +64 -0
  86. package/src/components/Workflow/Workflow.tsx +2 -0
  87. package/src/components/Workflow/Workflow.types.ts +10 -0
  88. package/src/components/Workflow/canvas/Canvas/Canvas.tsx +57 -53
  89. package/src/components/Workflow/createNode.test.ts +40 -2
  90. package/src/components/Workflow/createNode.ts +70 -2
  91. package/src/components/Workflow/index.ts +8 -0
  92. package/src/components/Workflow/layout/autoLayout.ts +4 -4
  93. package/src/components/Workflow/placement.test.ts +156 -0
  94. package/src/components/Workflow/placement.ts +172 -0
  95. package/src/components/Workflow/stories/Workflow.stories.tsx +79 -0
  96. package/src/components/Workflow/useWorkflowActions.test.tsx +345 -0
  97. package/src/components/Workflow/useWorkflowActions.ts +196 -0
  98. package/src/guidelines/FeedbackActions.mdx +133 -0
@@ -0,0 +1,103 @@
1
+ "use client";
2
+ import { useCallback, useMemo } from 'react';
3
+ import { useReactFlow, useStoreApi } from '@xyflow/react';
4
+ import { createNodeFromType, genId, toDefaultsTranslate } from './createNode.mjs';
5
+ import { findFreeExit, planNodePlacement, FALLBACK_NODE_SIZE } from './placement.mjs';
6
+ import { useWorkflow } from './WorkflowContext/WorkflowContext.mjs';
7
+
8
+ /** Pan duration when `addNode` brings the new node into view. */
9
+ const PAN_MS = 300;
10
+ /**
11
+ * Imperative editor actions for content rendered inside `<Workflow>` (its
12
+ * `children` and render slots sit inside the canvas's React Flow provider).
13
+ * Hosts use this instead of re-implementing node creation and placement — the
14
+ * tap-to-add palette on touch devices being the first caller.
15
+ *
16
+ * Must be called from a component rendered inside `<Workflow>`.
17
+ */
18
+ function useWorkflowActions() {
19
+ const { graph, dispatch, getNodeType, translate, hostBridge, readOnly, editor: { drawerTarget }, setSelectedNodeId, setSelectedEdgeId, setDrawerTarget, } = useWorkflow();
20
+ const { getNode, getViewport, setCenter } = useReactFlow();
21
+ const store = useStoreApi();
22
+ const getAddAfterSource = useCallback((nodeId) => {
23
+ const exit = findFreeExit(graph, getNodeType, nodeId);
24
+ if (!exit)
25
+ return null;
26
+ const def = getNodeType(exit.node.kind);
27
+ const label = def?.getInstanceTitle?.(exit.node.config) || def?.label || exit.node.id;
28
+ return { nodeId: exit.node.id, sourceHandle: exit.sourceHandle, label };
29
+ }, [graph, getNodeType]);
30
+ const canAddAfter = useCallback((nodeId) => getAddAfterSource(nodeId) !== null, [getAddAfterSource]);
31
+ const addNode = useCallback((kind, target, { inspector = 'follow' } = {}) => {
32
+ if (readOnly)
33
+ return null;
34
+ const def = getNodeType(kind);
35
+ if (!def)
36
+ return null;
37
+ const draft = createNodeFromType(def, {
38
+ position: { x: 0, y: 0 },
39
+ nodes: graph.nodes,
40
+ translate: toDefaultsTranslate(translate),
41
+ hostBridge,
42
+ });
43
+ // `nextNodeId` returns a fixed id for singletons (`start`); a second one
44
+ // would be dropped by the reducer's dedupe, so report it instead.
45
+ if (graph.nodes.some((n) => n.id === draft.id))
46
+ return null;
47
+ // Centre of the visible pane, in flow coordinates.
48
+ const { width, height } = store.getState();
49
+ const viewport = getViewport();
50
+ const viewportCenter = {
51
+ x: (width / 2 - viewport.x) / viewport.zoom,
52
+ y: (height / 2 - viewport.y) / viewport.zoom,
53
+ };
54
+ const { position, edge } = planNodePlacement({
55
+ graph,
56
+ getNodeType,
57
+ node: draft,
58
+ target,
59
+ viewportCenter,
60
+ edgeId: genId('e'),
61
+ getNodeSize: (id) => {
62
+ const { width: w, height: h } = getNode(id)?.measured ?? {};
63
+ return w && h ? { width: w, height: h } : undefined;
64
+ },
65
+ });
66
+ const node = { ...draft, position };
67
+ dispatch({
68
+ type: 'addNodeWithEdge',
69
+ node,
70
+ autoConnectFrom: edge ? { edge } : null,
71
+ });
72
+ // Select the new node so a follow-up `{ after: selected }` chains on.
73
+ setSelectedNodeId(node.id);
74
+ setSelectedEdgeId(null);
75
+ if (inspector === 'open' || (inspector === 'follow' && drawerTarget)) {
76
+ setDrawerTarget({ type: 'node', id: node.id });
77
+ }
78
+ else if (inspector === 'close') {
79
+ setDrawerTarget(null);
80
+ }
81
+ void setCenter(position.x + FALLBACK_NODE_SIZE.width / 2, position.y + FALLBACK_NODE_SIZE.height / 2, { zoom: viewport.zoom, duration: PAN_MS });
82
+ return { nodeId: node.id, edgeId: edge?.id ?? null };
83
+ }, [
84
+ readOnly,
85
+ getNodeType,
86
+ graph,
87
+ translate,
88
+ hostBridge,
89
+ store,
90
+ getViewport,
91
+ getNode,
92
+ dispatch,
93
+ setSelectedNodeId,
94
+ setSelectedEdgeId,
95
+ drawerTarget,
96
+ setDrawerTarget,
97
+ setCenter,
98
+ ]);
99
+ return useMemo(() => ({ addNode, getAddAfterSource, canAddAfter }), [addNode, getAddAfterSource, canAddAfter]);
100
+ }
101
+
102
+ export { useWorkflowActions };
103
+ //# sourceMappingURL=useWorkflowActions.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWorkflowActions.mjs","sources":["../../../src/components/Workflow/useWorkflowActions.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -173,6 +173,7 @@ var NodeShell_styles = require('./components/Workflow/canvas/NodeShell/NodeShell
173
173
  var workflowLabelProps = require('./components/Workflow/canvas/workflowLabelProps.js');
174
174
  var graphObserver = require('./components/Workflow/graphObserver.js');
175
175
  var graphReducer = require('./components/Workflow/graphReducer.js');
176
+ var useWorkflowActions = require('./components/Workflow/useWorkflowActions.js');
176
177
  var Workflow = require('./components/Workflow/Workflow.js');
177
178
  var Workflow_types = require('./components/Workflow/Workflow.types.js');
178
179
  var WorkflowContext = require('./components/Workflow/WorkflowContext/WorkflowContext.js');
@@ -382,6 +383,9 @@ exports.TagCloseButton = TagCloseButton.TagCloseButton;
382
383
  exports.TagLabel = TagLabel.TagLabel;
383
384
  exports.Textarea = Textarea.Textarea;
384
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;
385
389
  exports.closeButtonStyles = Toast_styles.closeButtonStyles;
386
390
  exports.toastStyles = Toast_styles.toastStyles;
387
391
  exports.Toaster = Toaster.Toaster;
@@ -424,6 +428,7 @@ exports.WorkflowGraphObserverContext = graphObserver.WorkflowGraphObserverContex
424
428
  exports.useWorkflowGraphObserver = graphObserver.useWorkflowGraphObserver;
425
429
  exports.emptyGraph = graphReducer.emptyGraph;
426
430
  exports.graphReducer = graphReducer.graphReducer;
431
+ exports.useWorkflowActions = useWorkflowActions.useWorkflowActions;
427
432
  exports.Workflow = Workflow.Workflow;
428
433
  exports.defineNodeType = Workflow_types.defineNodeType;
429
434
  exports.resolveDefaultConfig = Workflow_types.resolveDefaultConfig;
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';
@@ -171,6 +171,7 @@ export { CARD_WIDTH, MAX_META_CHIPS, PORT_STYLE, metaChipStyles } from './compon
171
171
  export { workflowLabelProps } from './components/Workflow/canvas/workflowLabelProps.mjs';
172
172
  export { WorkflowGraphObserverContext, useWorkflowGraphObserver } from './components/Workflow/graphObserver.mjs';
173
173
  export { emptyGraph, graphReducer } from './components/Workflow/graphReducer.mjs';
174
+ export { useWorkflowActions } from './components/Workflow/useWorkflowActions.mjs';
174
175
  export { Workflow } from './components/Workflow/Workflow.mjs';
175
176
  export { defineNodeType, resolveDefaultConfig } from './components/Workflow/Workflow.types.mjs';
176
177
  export { WorkflowProvider, useWorkflow, useWorkflowTranslate } from './components/Workflow/WorkflowContext/WorkflowContext.mjs';
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindlogic-ai/logician-ui",
3
- "version": "4.0.0-alpha.36",
3
+ "version": "4.0.0-alpha.38",
4
4
  "description": "A comprehensive React design system built on Chakra UI",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -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, and Toast.CloseTrigger
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 { toastStyles } from './Toast.styles';
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 = 5000,
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) => {