sh-ui-cli 0.52.1 → 0.52.2

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 (88) hide show
  1. package/data/changelog/versions.json +14 -0
  2. package/data/registry/react/components/_smoke/vanilla-extract.test.ts +33 -0
  3. package/data/registry/react/components/input/styles.css.ts +6 -6
  4. package/data/registry/react/registry.json +35 -852
  5. package/package.json +1 -1
  6. package/src/api.d.ts +3 -4
  7. package/src/constants.js +9 -5
  8. package/src/mcp.mjs +0 -1
  9. package/data/registry/react/components/accordion/index.vanilla-extract.tsx +0 -97
  10. package/data/registry/react/components/accordion/styles.css.ts +0 -131
  11. package/data/registry/react/components/avatar/index.vanilla-extract.tsx +0 -73
  12. package/data/registry/react/components/avatar/styles.css.ts +0 -68
  13. package/data/registry/react/components/badge/index.vanilla-extract.tsx +0 -40
  14. package/data/registry/react/components/badge/styles.css.ts +0 -71
  15. package/data/registry/react/components/breadcrumb/index.vanilla-extract.tsx +0 -152
  16. package/data/registry/react/components/breadcrumb/styles.css.ts +0 -95
  17. package/data/registry/react/components/calendar/index.vanilla-extract.tsx +0 -806
  18. package/data/registry/react/components/calendar/styles.css.ts +0 -250
  19. package/data/registry/react/components/carousel/index.vanilla-extract.tsx +0 -430
  20. package/data/registry/react/components/carousel/styles.css.ts +0 -169
  21. package/data/registry/react/components/checkbox/index.vanilla-extract.tsx +0 -96
  22. package/data/registry/react/components/checkbox/styles.css.ts +0 -74
  23. package/data/registry/react/components/code-editor/index.vanilla-extract.tsx +0 -230
  24. package/data/registry/react/components/code-editor/styles.css.ts +0 -97
  25. package/data/registry/react/components/code-panel/index.vanilla-extract.tsx +0 -191
  26. package/data/registry/react/components/code-panel/styles.css.ts +0 -151
  27. package/data/registry/react/components/color-picker/index.vanilla-extract.tsx +0 -467
  28. package/data/registry/react/components/color-picker/styles.css.ts +0 -169
  29. package/data/registry/react/components/combobox/index.vanilla-extract.tsx +0 -165
  30. package/data/registry/react/components/combobox/styles.css.ts +0 -174
  31. package/data/registry/react/components/context-menu/index.vanilla-extract.tsx +0 -251
  32. package/data/registry/react/components/context-menu/styles.css.ts +0 -167
  33. package/data/registry/react/components/date-picker/index.vanilla-extract.tsx +0 -520
  34. package/data/registry/react/components/date-picker/styles.css.ts +0 -111
  35. package/data/registry/react/components/dialog/index.vanilla-extract.tsx +0 -95
  36. package/data/registry/react/components/dialog/styles.css.ts +0 -140
  37. package/data/registry/react/components/dropdown-menu/index.vanilla-extract.tsx +0 -255
  38. package/data/registry/react/components/dropdown-menu/styles.css.ts +0 -175
  39. package/data/registry/react/components/file-upload/index.vanilla-extract.tsx +0 -487
  40. package/data/registry/react/components/file-upload/styles.css.ts +0 -193
  41. package/data/registry/react/components/form/index.vanilla-extract.tsx +0 -61
  42. package/data/registry/react/components/form/styles.css.ts +0 -56
  43. package/data/registry/react/components/header/index.vanilla-extract.tsx +0 -805
  44. package/data/registry/react/components/header/styles.css.ts +0 -413
  45. package/data/registry/react/components/label/index.vanilla-extract.tsx +0 -52
  46. package/data/registry/react/components/label/styles.css.ts +0 -141
  47. package/data/registry/react/components/markdown-editor/index.vanilla-extract.tsx +0 -119
  48. package/data/registry/react/components/markdown-editor/styles.css.ts +0 -231
  49. package/data/registry/react/components/menubar/index.vanilla-extract.tsx +0 -32
  50. package/data/registry/react/components/menubar/styles.css.ts +0 -53
  51. package/data/registry/react/components/numeric-input/index.vanilla-extract.tsx +0 -148
  52. package/data/registry/react/components/numeric-input/styles.css.ts +0 -65
  53. package/data/registry/react/components/page-toc/index.vanilla-extract.tsx +0 -174
  54. package/data/registry/react/components/page-toc/styles.css.ts +0 -97
  55. package/data/registry/react/components/pagination/index.vanilla-extract.tsx +0 -269
  56. package/data/registry/react/components/pagination/styles.css.ts +0 -113
  57. package/data/registry/react/components/popover/index.vanilla-extract.tsx +0 -113
  58. package/data/registry/react/components/popover/styles.css.ts +0 -78
  59. package/data/registry/react/components/progress/index.vanilla-extract.tsx +0 -54
  60. package/data/registry/react/components/progress/styles.css.ts +0 -53
  61. package/data/registry/react/components/radio/index.vanilla-extract.tsx +0 -65
  62. package/data/registry/react/components/radio/styles.css.ts +0 -79
  63. package/data/registry/react/components/rich-text-editor/index.vanilla-extract.tsx +0 -348
  64. package/data/registry/react/components/rich-text-editor/styles.css.ts +0 -243
  65. package/data/registry/react/components/select/index.vanilla-extract.tsx +0 -234
  66. package/data/registry/react/components/select/styles.css.ts +0 -225
  67. package/data/registry/react/components/separator/index.vanilla-extract.tsx +0 -46
  68. package/data/registry/react/components/separator/styles.css.ts +0 -24
  69. package/data/registry/react/components/sidebar/index.vanilla-extract.tsx +0 -1067
  70. package/data/registry/react/components/sidebar/styles.css.ts +0 -578
  71. package/data/registry/react/components/skeleton/index.vanilla-extract.tsx +0 -22
  72. package/data/registry/react/components/skeleton/styles.css.ts +0 -30
  73. package/data/registry/react/components/slider/index.vanilla-extract.tsx +0 -298
  74. package/data/registry/react/components/slider/styles.css.ts +0 -75
  75. package/data/registry/react/components/spinner/index.vanilla-extract.tsx +0 -38
  76. package/data/registry/react/components/spinner/styles.css.ts +0 -60
  77. package/data/registry/react/components/switch/index.vanilla-extract.tsx +0 -39
  78. package/data/registry/react/components/switch/styles.css.ts +0 -87
  79. package/data/registry/react/components/tabs/index.vanilla-extract.tsx +0 -91
  80. package/data/registry/react/components/tabs/styles.css.ts +0 -145
  81. package/data/registry/react/components/textarea/index.vanilla-extract.tsx +0 -23
  82. package/data/registry/react/components/textarea/styles.css.ts +0 -55
  83. package/data/registry/react/components/toast/index.vanilla-extract.tsx +0 -258
  84. package/data/registry/react/components/toast/styles.css.ts +0 -307
  85. package/data/registry/react/components/toggle/index.vanilla-extract.tsx +0 -131
  86. package/data/registry/react/components/toggle/styles.css.ts +0 -109
  87. package/data/registry/react/components/tooltip/index.vanilla-extract.tsx +0 -83
  88. package/data/registry/react/components/tooltip/styles.css.ts +0 -59
@@ -1,145 +0,0 @@
1
- import { style } from "@vanilla-extract/css";
2
-
3
- export const tabs = style({
4
- display: "flex",
5
- flexDirection: "column",
6
- gap: "var(--space-3)",
7
- width: "100%",
8
- selectors: {
9
- "&[data-orientation="vertical"]": {
10
- flexDirection: "row",
11
- },
12
- [`&[data-orientation="vertical"] > ${tabs__list}`]: {
13
- flexDirection: "column",
14
- alignItems: "stretch",
15
- },
16
- [`&[data-variant="underline"] > ${tabs__list}`]: {
17
- width: "100%",
18
- gap: 0,
19
- boxShadow: "inset 0 -1px 0 var(--border)",
20
- },
21
- [`&[data-variant="underline"] ${tabs__trigger}`]: {
22
- padding: "0.625rem var(--space-4)",
23
- },
24
- [`&[data-variant="underline"] ${tabs__indicator}`]: {
25
- top: "var(--active-tab-top)",
26
- left: "var(--active-tab-left)",
27
- width: "var(--active-tab-width)",
28
- height: "var(--active-tab-height)",
29
- boxShadow: "inset 0 -2px 0 var(--foreground)",
30
- },
31
- [`&[data-variant="underline"][data-orientation="vertical"] > ${tabs__list}`]: {
32
- width: "auto",
33
- boxShadow: "inset -1px 0 0 var(--border)",
34
- },
35
- [`&[data-variant="underline"][data-orientation="vertical"] ${tabs__indicator}`]: {
36
- boxShadow: "inset -2px 0 0 var(--foreground)",
37
- },
38
- [`&[data-variant="pill"] > ${tabs__list}`]: {
39
- padding: "var(--space-1)",
40
- background: "var(--background-muted, var(--background))",
41
- border: "1px solid var(--border)",
42
- borderRadius: "var(--radius)",
43
- },
44
- [`&[data-variant="pill"] ${tabs__trigger}`]: {
45
- padding: "0.375rem var(--space-3)",
46
- borderRadius: "calc(var(--radius) - 2px)",
47
- },
48
- [`&[data-variant="pill"] ${tabs__indicator}`]: {
49
- top: "var(--active-tab-top)",
50
- left: "var(--active-tab-left)",
51
- width: "var(--active-tab-width)",
52
- height: "var(--active-tab-height)",
53
- background: "var(--background)",
54
- borderRadius: "calc(var(--radius) - 2px)",
55
- boxShadow: "0 1px 2px rgba(0, 0, 0, 0.06)",
56
- },
57
- [`&[data-variant="plain"] ${tabs__trigger}`]: {
58
- padding: "0.375rem var(--space-2)",
59
- borderRadius: "calc(var(--radius) - 2px)",
60
- },
61
- [`&[data-variant="plain"] ${tabs__trigger}[data-selected]`]: {
62
- background: "var(--background-muted, transparent)",
63
- },
64
- [`&[data-variant="plain"] ${tabs__indicator}`]: {
65
- display: "none",
66
- },
67
- },
68
- });
69
-
70
- export const tabs__list = style({
71
- position: "relative",
72
- display: "inline-flex",
73
- alignItems: "center",
74
- gap: "var(--space-1)",
75
- width: "fit-content",
76
- });
77
-
78
- export const tabs__trigger = style({
79
- position: "relative",
80
- zIndex: 1,
81
- display: "inline-flex",
82
- alignItems: "center",
83
- justifyContent: "center",
84
- gap: "0.375rem",
85
- padding: "var(--space-2) var(--space-3)",
86
- background: "transparent",
87
- color: "var(--foreground-muted, var(--foreground))",
88
- border: 0,
89
- fontSize: "var(--text-sm)",
90
- fontWeight: "var(--weight-medium)",
91
- lineHeight: 1,
92
- cursor: "pointer",
93
- transition: "color var(--duration-fast), background-color var(--duration-fast)",
94
- userSelect: "none",
95
- WebkitTapHighlightColor: "transparent",
96
- whiteSpace: "nowrap",
97
- selectors: {
98
- "&:hover:not(:disabled):not([data-selected])": {
99
- color: "var(--foreground)",
100
- },
101
- "&:focus-visible": {
102
- outline: "var(--border-width-strong) solid var(--foreground)",
103
- outlineOffset: "2px",
104
- },
105
- "&[data-selected]": {
106
- color: "var(--foreground)",
107
- },
108
- "&:disabled": {
109
- opacity: "var(--opacity-disabled)",
110
- cursor: "not-allowed",
111
- },
112
- },
113
- });
114
-
115
- export const tabs__indicator = style({
116
- position: "absolute",
117
- transition: "top 180ms, left 180ms, width 180ms, height 180ms",
118
- zIndex: 0,
119
- pointerEvents: "none",
120
- selectors: {
121
- "&[data-activation-direction="none"]": {
122
- transition: "none",
123
- },
124
- },
125
- });
126
-
127
- export const tabs__content = style({
128
- outline: "none",
129
- selectors: {
130
- "&:focus-visible": {
131
- outline: "var(--border-width-strong) solid var(--foreground)",
132
- outlineOffset: "2px",
133
- borderRadius: "var(--radius)",
134
- },
135
- },
136
- });
137
-
138
- /** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
139
- export const byKey: Record<string, string> = {
140
- "tabs": tabs,
141
- "tabs__list": tabs__list,
142
- "tabs__trigger": tabs__trigger,
143
- "tabs__indicator": tabs__indicator,
144
- "tabs__content": tabs__content,
145
- };
@@ -1,23 +0,0 @@
1
- import * as React from "react";
2
- import { byKey, textarea } from "./styles.css";
3
-
4
- import { cn } from "@SH_UI_UTILS@";
5
- export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
6
-
7
-
8
- /**
9
- * 여러 줄 텍스트 입력. 네이티브 `<textarea>` 위에 sh-ui 토큰 스타일만 입혔다.
10
- * 라벨/오류 메시지는 외부에서 조립한다 — 단독 사용 시 `<Label>`과 `aria-describedby` 연결 권장.
11
- */
12
- export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
13
- ({ className, ...props }, ref) => {
14
- return (
15
- <textarea
16
- ref={ref}
17
- className={cn(textarea, className)}
18
- {...props}
19
- />
20
- );
21
- },
22
- );
23
- Textarea.displayName = "Textarea";
@@ -1,55 +0,0 @@
1
- import { style } from "@vanilla-extract/css";
2
-
3
- export const textarea = style({
4
- display: "block",
5
- width: "100%",
6
- minHeight: "5rem",
7
- padding: "var(--space-2) var(--space-3)",
8
- background: "var(--background)",
9
- color: "var(--foreground)",
10
- border: "1px solid var(--border)",
11
- borderRadius: "var(--radius)",
12
- fontFamily: "inherit",
13
- fontSize: "var(--text-sm)",
14
- lineHeight: 1.5,
15
- resize: "vertical",
16
- transition: "border-color var(--duration-fast), box-shadow var(--duration-fast)",
17
- WebkitTapHighlightColor: "transparent",
18
- selectors: {
19
- "&::placeholder": {
20
- color: "var(--foreground-subtle)",
21
- },
22
- "&:hover:not(:disabled):not(:focus)": {
23
- borderColor: "var(--border-strong)",
24
- },
25
- "&:focus": {
26
- outline: "none",
27
- borderColor: "var(--foreground)",
28
- boxShadow: "0 0 0 1px var(--foreground)",
29
- },
30
- "&:disabled": {
31
- opacity: "var(--opacity-disabled)",
32
- cursor: "not-allowed",
33
- background: "var(--background-subtle)",
34
- },
35
- "&:read-only": {
36
- background: "var(--background-subtle)",
37
- },
38
- "&[aria-invalid="true"]": {
39
- borderColor: "var(--danger)",
40
- },
41
- "&[aria-invalid="true"]:focus": {
42
- boxShadow: "0 0 0 1px var(--danger)",
43
- },
44
- },
45
- "@media": {
46
- "(hover: none) and (pointer: coarse)": {
47
- fontSize: "var(--text-base)",
48
- },
49
- },
50
- });
51
-
52
- /** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
53
- export const byKey: Record<string, string> = {
54
- "textarea": textarea,
55
- };
@@ -1,258 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { createPortal } from "react-dom";
5
- import { cn } from "@SH_UI_UTILS@";
6
- import { byKey, toastViewport, toast, toast__icon, toastSuccess, toastDanger, toastWarning, toast__body, toast__title, toast__description, toast__action, toast__close } from "./styles.css";
7
-
8
- /* ───────── Types ───────── */
9
-
10
- type ToastVariant = "default" | "success" | "danger" | "warning";
11
-
12
- interface ToastItem {
13
- id: string;
14
- /** 짧은 제목. 한 줄 굵게 표시. */
15
- title?: React.ReactNode;
16
- /** 본문 설명. 줄바꿈 가능. */
17
- description?: React.ReactNode;
18
- variant: ToastVariant;
19
- duration: number;
20
- /** 우측에 표시될 액션 노드(예: "다시 시도" 버튼). */
21
- action?: React.ReactNode;
22
- }
23
-
24
- type ToastInput = Omit<ToastItem, "id" | "variant" | "duration"> & {
25
- /**
26
- * 토스트 종류.
27
- * - `default` — 정보성 알림
28
- * - `success` — 성공
29
- * - `warning` — 주의
30
- * - `danger` — 오류 (스크린리더에 즉시 읽힘)
31
- *
32
- * @default "default"
33
- */
34
- variant?: ToastVariant;
35
- /**
36
- * 자동 닫힘 시간(ms). `0` 이하면 자동 닫힘 비활성.
37
- * @default 4000
38
- */
39
- duration?: number;
40
- };
41
-
42
- /* ───────── Store (외부 상태) ───────── */
43
-
44
- type Listener = () => void;
45
-
46
- let toasts: ToastItem[] = [];
47
- const listeners = new Set<Listener>();
48
-
49
- const notify = () => listeners.forEach((l) => l());
50
-
51
- let counter = 0;
52
- const genId = () => `sh-toast-${++counter}`;
53
-
54
- function addToast(input: ToastInput): string {
55
- const id = genId();
56
- toasts = [
57
- ...toasts,
58
- {
59
- id,
60
- variant: input.variant ?? "default",
61
- duration: input.duration ?? 4000,
62
- title: input.title,
63
- description: input.description,
64
- action: input.action,
65
- },
66
- ];
67
- notify();
68
- return id;
69
- }
70
-
71
- function removeToast(id: string) {
72
- toasts = toasts.filter((t) => t.id !== id);
73
- notify();
74
- }
75
-
76
- function useToastStore() {
77
- return React.useSyncExternalStore(
78
- (cb) => {
79
- listeners.add(cb);
80
- return () => listeners.delete(cb);
81
- },
82
- () => toasts,
83
- () => toasts,
84
- );
85
- }
86
-
87
- /* ───────── Public API ───────── */
88
-
89
- /**
90
- * 토스트 알림을 발생시키는 명령형 API. 문자열만 전달하면 description으로 사용된다.
91
- * variant별 헬퍼(`toast.success` 등)와 `toast.dismiss(id)`도 함께 노출된다.
92
- * 사용 전에 앱 어딘가에 `<Toaster />`가 마운트되어 있어야 화면에 보인다.
93
- *
94
- * @param input - 문자열(빠른 알림) 또는 `{ title, description, variant, duration, action }`.
95
- * @returns 생성된 토스트 id. `toast.dismiss(id)`로 수동 닫을 때 사용.
96
- * @example
97
- * toast.success("저장 완료");
98
- * toast({ title: "오류", description: e.message, variant: "danger" });
99
- */
100
- export function toast(input: ToastInput | string): string {
101
- if (typeof input === "string") return addToast({ description: input });
102
- return addToast(input);
103
- }
104
-
105
- toast.success = (input: ToastInput | string) =>
106
- toast(typeof input === "string" ? { description: input, variant: "success" } : { ...input, variant: "success" });
107
-
108
- toast.danger = (input: ToastInput | string) =>
109
- toast(typeof input === "string" ? { description: input, variant: "danger" } : { ...input, variant: "danger" });
110
-
111
- toast.warning = (input: ToastInput | string) =>
112
- toast(typeof input === "string" ? { description: input, variant: "warning" } : { ...input, variant: "warning" });
113
-
114
- toast.dismiss = removeToast;
115
-
116
- /* ───────── Icons ───────── */
117
-
118
- function CheckIcon() {
119
- return (
120
- <svg viewBox="0 0 16 16" width="16" height="16" fill="none" aria-hidden>
121
- <circle cx="8" cy="8" r="7.25" stroke="currentColor" strokeWidth="1.5" />
122
- <path d="M5 8.5 7 10.5 11 6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
123
- </svg>
124
- );
125
- }
126
-
127
- function AlertIcon() {
128
- return (
129
- <svg viewBox="0 0 16 16" width="16" height="16" fill="none" aria-hidden>
130
- <circle cx="8" cy="8" r="7.25" stroke="currentColor" strokeWidth="1.5" />
131
- <path d="M8 5v3.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
132
- <circle cx="8" cy="11" r="0.75" fill="currentColor" />
133
- </svg>
134
- );
135
- }
136
-
137
- function XIcon() {
138
- return (
139
- <svg viewBox="0 0 16 16" width="16" height="16" fill="none" aria-hidden>
140
- <circle cx="8" cy="8" r="7.25" stroke="currentColor" strokeWidth="1.5" />
141
- <path d="M6 6l4 4M10 6l-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
142
- </svg>
143
- );
144
- }
145
-
146
- const VARIANT_ICON: Record<ToastVariant, React.ReactNode> = {
147
- default: null,
148
- success: <CheckIcon />,
149
- danger: <XIcon />,
150
- warning: <AlertIcon />,
151
- };
152
-
153
- /* ───────── Single Toast ───────── */
154
-
155
- function ToastCard({ item, onDismiss }: { item: ToastItem; onDismiss: () => void }) {
156
- const [exiting, setExiting] = React.useState(false);
157
-
158
- React.useEffect(() => {
159
- if (item.duration <= 0) return;
160
- const timer = setTimeout(() => setExiting(true), item.duration);
161
- return () => clearTimeout(timer);
162
- }, [item.duration]);
163
-
164
- const handleAnimationEnd = () => {
165
- if (exiting) onDismiss();
166
- };
167
-
168
- const icon = VARIANT_ICON[item.variant];
169
-
170
- return (
171
- <div
172
- className={cn(toast, byKey[`toast--${item.variant}`])}
173
- role={item.variant === "danger" ? "alert" : "status"}
174
- aria-live={item.variant === "danger" ? "assertive" : "polite"}
175
- data-exiting={exiting || undefined}
176
- onAnimationEnd={handleAnimationEnd}
177
- >
178
- {icon && <span className={toast__icon}>{icon}</span>}
179
- <div className={toast__body}>
180
- {item.title && <p className={toast__title}>{item.title}</p>}
181
- {item.description && <p className={toast__description}>{item.description}</p>}
182
- </div>
183
- {item.action && <div className={toast__action}>{item.action}</div>}
184
- <button
185
- type="button"
186
- className={toast__close}
187
- onClick={() => setExiting(true)}
188
- aria-label="닫기"
189
- >
190
- ×
191
- </button>
192
- </div>
193
- );
194
- }
195
-
196
- /* ───────── Toaster (Provider) ───────── */
197
-
198
- export type ToastPosition =
199
- | "top-left"
200
- | "top-right"
201
- | "top-center"
202
- | "bottom-left"
203
- | "bottom-right"
204
- | "bottom-center";
205
-
206
- export interface ToasterProps {
207
- /**
208
- * 토스트 표시 위치.
209
- * `top-*` 위치는 새 토스트가 위에 쌓이고, `bottom-*` 위치는 아래에 쌓인다.
210
- *
211
- * @default "bottom-right"
212
- */
213
- position?: ToastPosition;
214
- /**
215
- * Portal이 마운트될 DOM 노드.
216
- * @default document.body
217
- */
218
- container?: Element | null;
219
- }
220
-
221
- /**
222
- * `toast()`로 발생한 알림을 그리는 viewport. 앱 루트에 한 번만 마운트한다.
223
- * `danger` variant는 `role="alert"`+`aria-live="assertive"`로 즉시 읽히고, 그 외는 `polite`로 큐잉된다.
224
- */
225
- export function Toaster({ position = "bottom-right", container }: ToasterProps) {
226
- const items = useToastStore();
227
- const [mounted, setMounted] = React.useState(false);
228
-
229
- React.useEffect(() => setMounted(true), []);
230
-
231
- if (!mounted || items.length === 0) return null;
232
-
233
- const isBottom = position.startsWith("bottom");
234
-
235
- const el = (
236
- <div
237
- className={toastViewport}
238
- data-position={position}
239
- aria-label="알림"
240
- >
241
- {isBottom ? items.map((item) => (
242
- <ToastCard
243
- key={item.id}
244
- item={item}
245
- onDismiss={() => removeToast(item.id)}
246
- />
247
- )) : [...items].reverse().map((item) => (
248
- <ToastCard
249
- key={item.id}
250
- item={item}
251
- onDismiss={() => removeToast(item.id)}
252
- />
253
- ))}
254
- </div>
255
- );
256
-
257
- return createPortal(el, container ?? document.body);
258
- }