@tide-tds/components 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ import { Role } from "./types.js";
2
+ import { ComponentProps, MouseEvent, ReactElement, ReactNode } from "react";
3
+ /** Toast의 액션 버튼 */
4
+ export interface ToastAction {
5
+ /** 버튼 글자. 예: 「되돌리기」, 「다시 시도」 */
6
+ label: ReactNode;
7
+ /**
8
+ * 누르면 부른다. 부른 뒤 Toast를 닫는다. `event.preventDefault()`를 부르면 닫지 않는다.
9
+ */
10
+ onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
11
+ }
12
+ /** `queue.add`에 넘기는 Toast 하나 */
13
+ export interface ToastOptions {
14
+ /** 제목. Toast의 이름이 되고 굵은 글자로 보인다. */
15
+ title: ReactNode;
16
+ /** 본문. Toast의 설명이 된다. */
17
+ description?: ReactNode;
18
+ /**
19
+ * Role. Tide Role 6개와 앱이 `RoleRegistry`에 더한 Role을 받는다. 기본값은 `info`다(Alert와 같다).
20
+ * 상태 아이콘과 그 색, 닫기·액션 버튼 색을 정한다. `danger`(오류)는 자동으로 닫지 않는다.
21
+ * 색 계열 이름(`blue` 등)은 받지 않는다(ADR 0016).
22
+ */
23
+ color?: Role;
24
+ /** 액션 버튼. 있으면 자동으로 닫지 않는다. */
25
+ action?: ToastAction;
26
+ /**
27
+ * 자동으로 닫을 시간(ms). 기본값은 5000이고, 더 짧은 값은 5000으로 올린다. `null`이면 자동으로 닫지 않는다.
28
+ * 액션이 있거나 `color="danger"`면 이 값과 관계없이 자동으로 닫지 않는다.
29
+ * 시간은 영역에 마우스를 올리거나 포커스가 있는 동안 멈춘다.
30
+ */
31
+ timeout?: number | null;
32
+ /** 닫힐 때(닫기 버튼, 액션, 자동 닫힘, `queue.close`) 부른다. */
33
+ onClose?: () => void;
34
+ /** 닫기 버튼 이름. 기본값은 `"닫기"`다. */
35
+ closeLabel?: string;
36
+ }
37
+ /** `new ToastQueue(options)`의 인자 */
38
+ export interface ToastQueueOptions {
39
+ /** 한 번에 보이는 Toast 수. 넘는 Toast는 앞의 것이 닫힐 때까지 기다린다. 기본값은 3이다. */
40
+ maxVisible?: number;
41
+ }
42
+ /**
43
+ * Toast 큐. React 밖(모듈)에서 만들어 `ToastRegion`에 넘기고, 어디서든 `add`로 띄운다.
44
+ *
45
+ * ```tsx
46
+ * export const toasts = new ToastQueue();
47
+ * toasts.add({ title: "저장했습니다" });
48
+ * ```
49
+ */
50
+ export declare class ToastQueue {
51
+ constructor({ maxVisible }?: ToastQueueOptions);
52
+ /** Toast를 띄우고 key를 돌려준다. 새 Toast가 맨 앞(영역의 아래)에 온다. */
53
+ add(options: ToastOptions): string;
54
+ /** key의 Toast를 닫는다. */
55
+ close(key: string): void;
56
+ /** 모든 Toast를 닫는다. 기다리던 Toast도 빠진다. `onClose`는 부르지 않는다. */
57
+ clear(): void;
58
+ }
59
+ export interface ToastRegionProps extends Omit<ComponentProps<"div">, "children" | "role"> {
60
+ /** 그릴 큐(`new ToastQueue()`) */
61
+ queue: ToastQueue;
62
+ /**
63
+ * 영역(랜드마크) 이름. 기본값은 React Aria 번역 「N개 알림.」(locale을 따른다)이다.
64
+ */
65
+ "aria-label"?: string;
66
+ }
67
+ /**
68
+ * `ToastQueue`의 Toast를 그리는 영역. 앱에 하나 둔다(보통 레이아웃의 client Component 안).
69
+ * Toast가 있을 때만 `document.body`에 portal하고, 없으면 아무것도 그리지 않는다.
70
+ */
71
+ export declare function ToastRegion({ queue, ...props }: ToastRegionProps): ReactElement | null;
package/dist/Toast.js ADDED
@@ -0,0 +1,191 @@
1
+ "use client";
2
+
3
+ import { alertCloseClass, alertContentClass, alertDescriptionClass, alertIconClass, alertLayoutClass, alertStatusIcon, alertTitleClass } from "./alert-style.js";
4
+ import { Button } from "./Button.js";
5
+ import { cn } from "./cn.js";
6
+ import { IconButton } from "./IconButton.js";
7
+ import { OverlayPortal, useOverlayPresence } from "./overlay.js";
8
+ import { overlaySurfaceClass, popoverMotionClass } from "./overlay-style.js";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
11
+ import { XIcon } from "@tide-tds/icons";
12
+ import { mergeProps, mergeRefs, useToast, useToastRegion } from "react-aria";
13
+ import { ToastQueue, useToastQueue } from "react-stately";
14
+
15
+ // src/Toast.tsx
16
+ /** 자동으로 닫히는 Toast가 보이는 가장 짧은 시간(ms). React Aria 문서가 권하는 최소값과 같다. */
17
+ const minTimeout = 5e3;
18
+ const statelyQueues = /* @__PURE__ */ new WeakMap();
19
+ /** 자동 닫힘 시간. `undefined`면 자동으로 닫지 않는다. */
20
+ function resolveTimeout(options) {
21
+ if (options.action !== void 0 || options.color === "danger") return void 0;
22
+ const timeout = options.timeout === void 0 ? minTimeout : options.timeout;
23
+ if (timeout === null || !Number.isFinite(timeout)) return void 0;
24
+ return Math.max(timeout, minTimeout);
25
+ }
26
+ /**
27
+ * Toast 큐. React 밖(모듈)에서 만들어 `ToastRegion`에 넘기고, 어디서든 `add`로 띄운다.
28
+ *
29
+ * ```tsx
30
+ * export const toasts = new ToastQueue();
31
+ * toasts.add({ title: "저장했습니다" });
32
+ * ```
33
+ */
34
+ var ToastQueue$1 = class {
35
+ constructor({ maxVisible = 3 } = {}) {
36
+ statelyQueues.set(this, new ToastQueue({ maxVisibleToasts: maxVisible }));
37
+ }
38
+ /** Toast를 띄우고 key를 돌려준다. 새 Toast가 맨 앞(영역의 아래)에 온다. */
39
+ add(options) {
40
+ const { timeout: _timeout, onClose, ...content } = options;
41
+ return queueOf(this).add(content, {
42
+ timeout: resolveTimeout(options),
43
+ onClose
44
+ });
45
+ }
46
+ /** key의 Toast를 닫는다. */
47
+ close(key) {
48
+ queueOf(this).close(key);
49
+ }
50
+ /** 모든 Toast를 닫는다. 기다리던 Toast도 빠진다. `onClose`는 부르지 않는다. */
51
+ clear() {
52
+ queueOf(this).clear();
53
+ }
54
+ };
55
+ function queueOf(queue) {
56
+ const stately = statelyQueues.get(queue);
57
+ if (!stately) throw new Error("ToastRegion의 queue에는 new ToastQueue()로 만든 큐를 넘긴다.");
58
+ return stately;
59
+ }
60
+ const regionClass = "fixed end-4 bottom-4 isolate z-toast flex w-[calc(100%-2rem)] max-w-96 flex-col-reverse gap-2 rounded-lg outline-none focus-visible:focus-ring";
61
+ const toastClass = `${alertLayoutClass} ${overlaySurfaceClass} ${popoverMotionClass} outline-none focus-visible:focus-ring`;
62
+ /** 보이는 Toast 목록에 퇴장 중인 Toast를 원래 자리에 끼워 넣는다. */
63
+ function mergeEntries(previous, visible) {
64
+ const visibleKeys = new Set(visible.map((toast) => toast.key));
65
+ const next = visible.map((toast) => ({
66
+ toast,
67
+ open: true
68
+ }));
69
+ previous.forEach((entry, index) => {
70
+ if (visibleKeys.has(entry.toast.key)) return;
71
+ next.splice(Math.min(index, next.length), 0, {
72
+ toast: entry.toast,
73
+ open: false
74
+ });
75
+ });
76
+ return next;
77
+ }
78
+ /**
79
+ * `ToastQueue`의 Toast를 그리는 영역. 앱에 하나 둔다(보통 레이아웃의 client Component 안).
80
+ * Toast가 있을 때만 `document.body`에 portal하고, 없으면 아무것도 그리지 않는다.
81
+ */
82
+ function ToastRegion({ queue, ...props }) {
83
+ const state = useToastQueue(queueOf(queue));
84
+ const [entries, setEntries] = useState(() => state.visibleToasts.map((toast) => ({
85
+ toast,
86
+ open: true
87
+ })));
88
+ const [visible, setVisible] = useState(state.visibleToasts);
89
+ if (visible !== state.visibleToasts) {
90
+ setVisible(state.visibleToasts);
91
+ setEntries(mergeEntries(entries, state.visibleToasts));
92
+ }
93
+ const onExited = useCallback((key) => {
94
+ setEntries((current) => current.filter((entry) => entry.open || entry.toast.key !== key));
95
+ }, []);
96
+ if (entries.length === 0) return null;
97
+ return /* @__PURE__ */ jsx(OverlayPortal, {
98
+ isExiting: false,
99
+ focusManagement: false,
100
+ children: /* @__PURE__ */ jsx(Region, {
101
+ ...props,
102
+ state,
103
+ entries,
104
+ onExited
105
+ })
106
+ });
107
+ }
108
+ function Region({ state, entries, onExited, ref, className, "aria-label": ariaLabel, ...props }) {
109
+ const regionRef = useRef(null);
110
+ const mergedRef = useMemo(() => mergeRefs(ref, regionRef), [ref]);
111
+ const { regionProps } = useToastRegion({ "aria-label": ariaLabel }, state, regionRef);
112
+ return /* @__PURE__ */ jsx("div", {
113
+ ...mergeProps(props, regionProps),
114
+ ref: mergedRef,
115
+ className: cn(regionClass, className),
116
+ children: entries.map((entry) => /* @__PURE__ */ jsx(ToastItem, {
117
+ toast: entry.toast,
118
+ open: entry.open,
119
+ state,
120
+ onExited
121
+ }, entry.toast.key))
122
+ });
123
+ }
124
+ function ToastItem({ toast, open, state, onExited }) {
125
+ const ref = useRef(null);
126
+ const present = useOverlayPresence(open, ref);
127
+ useEffect(() => {
128
+ if (!present) onExited(toast.key);
129
+ }, [
130
+ present,
131
+ onExited,
132
+ toast.key
133
+ ]);
134
+ const { toastProps, contentProps, titleProps, descriptionProps } = useToast({ toast }, state, ref);
135
+ const { title, description, color = "info", action, closeLabel = "닫기" } = toast.content;
136
+ const StatusIcon = alertStatusIcon(color);
137
+ const close = () => state.close(toast.key);
138
+ return /* @__PURE__ */ jsxs("div", {
139
+ ...open ? toastProps : {},
140
+ ref,
141
+ "data-color": color,
142
+ "data-open": open ? "" : void 0,
143
+ inert: !open,
144
+ "data-placement": "top",
145
+ className: toastClass,
146
+ children: [
147
+ /* @__PURE__ */ jsx(StatusIcon, {
148
+ size: 20,
149
+ "aria-hidden": "true",
150
+ className: `${alertIconClass} text-accent`
151
+ }),
152
+ /* @__PURE__ */ jsxs("div", {
153
+ className: "flex min-w-0 flex-1 flex-col gap-3",
154
+ children: [/* @__PURE__ */ jsxs("div", {
155
+ ...open ? contentProps : {},
156
+ className: alertContentClass,
157
+ children: [/* @__PURE__ */ jsx("div", {
158
+ ...titleProps,
159
+ className: alertTitleClass,
160
+ children: title
161
+ }), description !== void 0 && /* @__PURE__ */ jsx("div", {
162
+ ...descriptionProps,
163
+ className: "text-body-sm",
164
+ children: description
165
+ })]
166
+ }), action !== void 0 && /* @__PURE__ */ jsx(Button, {
167
+ variant: "outline",
168
+ color,
169
+ size: "sm",
170
+ className: "self-start",
171
+ onClick: (event) => {
172
+ action.onClick?.(event);
173
+ if (!event.defaultPrevented) close();
174
+ },
175
+ children: action.label
176
+ })]
177
+ }),
178
+ /* @__PURE__ */ jsx(IconButton, {
179
+ "aria-label": closeLabel,
180
+ variant: "ghost",
181
+ color,
182
+ size: "xs",
183
+ onClick: close,
184
+ className: alertCloseClass,
185
+ children: /* @__PURE__ */ jsx(XIcon, {})
186
+ })
187
+ ]
188
+ });
189
+ }
190
+
191
+ export { ToastQueue$1 as ToastQueue, ToastRegion };
package/dist/index.d.ts CHANGED
@@ -32,6 +32,7 @@ import { Dialog, DialogClose, DialogCloseProps, DialogContent, DialogContentProp
32
32
  import { Accordion, AccordionHeadingLevel, AccordionItem, AccordionItemProps, AccordionPanel, AccordionPanelProps, AccordionProps, AccordionTrigger, AccordionTriggerProps } from "./Accordion.js";
33
33
  import { Tab, TabList, TabListProps, TabPanel, TabPanelProps, TabProps, Tabs, TabsActivationMode, TabsOrientation, TabsProps } from "./Tabs.js";
34
34
  import { Tag, TagGroup, TagGroupProps, TagProps } from "./Tag.js";
35
+ import { ToastAction, ToastOptions, ToastQueue, ToastQueueOptions, ToastRegion, ToastRegionProps } from "./Toast.js";
35
36
  import { Tooltip, TooltipContent, TooltipContentProps, TooltipPlacement, TooltipProps, TooltipTrigger, TooltipTriggerProps } from "./Tooltip.js";
36
37
  import { Color, ColorFamilyName, ColorFamilyRegistry, FillVariant, Role, RoleRegistry, Size } from "./types.js";
37
- export { Accordion, type AccordionHeadingLevel, AccordionItem, type AccordionItemProps, AccordionPanel, type AccordionPanelProps, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, type AlertDialogProps, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbCurrent, type BreadcrumbCurrentProps, BreadcrumbItem, type BreadcrumbItemProps, BreadcrumbLink, type BreadcrumbLinkProps, type BreadcrumbProps, Button, type ButtonProps, Card, type CardProps, Checkbox, CheckboxGroup, type CheckboxGroupProps, type CheckboxProps, type ChoiceOrientation, type Color, type ColorFamilyName, type ColorFamilyRegistry, Combobox, ComboboxItem, type ComboboxItemProps, type ComboboxProps, ComboboxSection, type ComboboxSectionProps, Dialog, DialogClose, type DialogCloseProps, DialogContent, type DialogContentProps, DialogDescription, type DialogDescriptionProps, DialogFooter, type DialogFooterProps, type DialogProps, type DialogSize, DialogTitle, type DialogTitleProps, DialogTrigger, type DialogTriggerProps, Field, type FieldProps, type FillVariant, IconButton, type IconButtonProps, Input, type InputProps, Link, type LinkProps, Menu, MenuContent, type MenuContentProps, MenuItem, type MenuItemProps, type MenuPlacement, type MenuProps, MenuSection, type MenuSectionProps, MenuTrigger, type MenuTriggerProps, Pagination, PaginationEllipsis, type PaginationEllipsisProps, PaginationItem, type PaginationItemProps, PaginationLink, type PaginationLinkProps, PaginationNext, type PaginationNextProps, PaginationPrevious, type PaginationPreviousProps, type PaginationProps, Popover, PopoverContent, type PopoverContentProps, type PopoverPlacement, type PopoverProps, PopoverTrigger, type PopoverTriggerProps, Progress, type ProgressProps, Radio, RadioGroup, type RadioGroupProps, type RadioProps, type Role, type RoleRegistry, Select, SelectItem, type SelectItemProps, type SelectProps, SelectSection, type SelectSectionProps, Separator, type SeparatorProps, type Size, Skeleton, type SkeletonProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeaderCell, type TableHeaderCellProps, type TableProps, TableRow, type TableRowProps, Tabs, type TabsActivationMode, type TabsOrientation, type TabsProps, Tag, TagGroup, type TagGroupProps, type TagProps, Textarea, type TextareaProps, Tooltip, TooltipContent, type TooltipContentProps, type TooltipPlacement, type TooltipProps, TooltipTrigger, type TooltipTriggerProps, Typography, type TypographyProps, VisuallyHidden, type VisuallyHiddenProps, cn };
38
+ export { Accordion, type AccordionHeadingLevel, AccordionItem, type AccordionItemProps, AccordionPanel, type AccordionPanelProps, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, type AlertDialogProps, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbCurrent, type BreadcrumbCurrentProps, BreadcrumbItem, type BreadcrumbItemProps, BreadcrumbLink, type BreadcrumbLinkProps, type BreadcrumbProps, Button, type ButtonProps, Card, type CardProps, Checkbox, CheckboxGroup, type CheckboxGroupProps, type CheckboxProps, type ChoiceOrientation, type Color, type ColorFamilyName, type ColorFamilyRegistry, Combobox, ComboboxItem, type ComboboxItemProps, type ComboboxProps, ComboboxSection, type ComboboxSectionProps, Dialog, DialogClose, type DialogCloseProps, DialogContent, type DialogContentProps, DialogDescription, type DialogDescriptionProps, DialogFooter, type DialogFooterProps, type DialogProps, type DialogSize, DialogTitle, type DialogTitleProps, DialogTrigger, type DialogTriggerProps, Field, type FieldProps, type FillVariant, IconButton, type IconButtonProps, Input, type InputProps, Link, type LinkProps, Menu, MenuContent, type MenuContentProps, MenuItem, type MenuItemProps, type MenuPlacement, type MenuProps, MenuSection, type MenuSectionProps, MenuTrigger, type MenuTriggerProps, Pagination, PaginationEllipsis, type PaginationEllipsisProps, PaginationItem, type PaginationItemProps, PaginationLink, type PaginationLinkProps, PaginationNext, type PaginationNextProps, PaginationPrevious, type PaginationPreviousProps, type PaginationProps, Popover, PopoverContent, type PopoverContentProps, type PopoverPlacement, type PopoverProps, PopoverTrigger, type PopoverTriggerProps, Progress, type ProgressProps, Radio, RadioGroup, type RadioGroupProps, type RadioProps, type Role, type RoleRegistry, Select, SelectItem, type SelectItemProps, type SelectProps, SelectSection, type SelectSectionProps, Separator, type SeparatorProps, type Size, Skeleton, type SkeletonProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeaderCell, type TableHeaderCellProps, type TableProps, TableRow, type TableRowProps, Tabs, type TabsActivationMode, type TabsOrientation, type TabsProps, Tag, TagGroup, type TagGroupProps, type TagProps, Textarea, type TextareaProps, type ToastAction, type ToastOptions, ToastQueue, type ToastQueueOptions, ToastRegion, type ToastRegionProps, Tooltip, TooltipContent, type TooltipContentProps, type TooltipPlacement, type TooltipProps, TooltipTrigger, type TooltipTriggerProps, Typography, type TypographyProps, VisuallyHidden, type VisuallyHiddenProps, cn };
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, Di
32
32
  import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger } from "./Accordion.js";
33
33
  import { Tab, TabList, TabPanel, Tabs } from "./Tabs.js";
34
34
  import { Tag, TagGroup } from "./Tag.js";
35
+ import { ToastQueue, ToastRegion } from "./Toast.js";
35
36
  import { Tooltip, TooltipContent, TooltipTrigger } from "./Tooltip.js";
36
37
 
37
- export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, Alert, AlertDialog, Avatar, Badge, Breadcrumb, BreadcrumbCurrent, BreadcrumbItem, BreadcrumbLink, Button, Card, Checkbox, CheckboxGroup, Combobox, ComboboxItem, ComboboxSection, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogTitle, DialogTrigger, Field, IconButton, Input, Link, Menu, MenuContent, MenuItem, MenuSection, MenuTrigger, Pagination, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverContent, PopoverTrigger, Progress, Radio, RadioGroup, Select, SelectItem, SelectSection, Separator, Skeleton, Spinner, Switch, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Tabs, Tag, TagGroup, Textarea, Tooltip, TooltipContent, TooltipTrigger, Typography, VisuallyHidden, cn };
38
+ export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, Alert, AlertDialog, Avatar, Badge, Breadcrumb, BreadcrumbCurrent, BreadcrumbItem, BreadcrumbLink, Button, Card, Checkbox, CheckboxGroup, Combobox, ComboboxItem, ComboboxSection, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogTitle, DialogTrigger, Field, IconButton, Input, Link, Menu, MenuContent, MenuItem, MenuSection, MenuTrigger, Pagination, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverContent, PopoverTrigger, Progress, Radio, RadioGroup, Select, SelectItem, SelectSection, Separator, Skeleton, Spinner, Switch, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Tabs, Tag, TagGroup, Textarea, ToastQueue, ToastRegion, Tooltip, TooltipContent, TooltipTrigger, Typography, VisuallyHidden, cn };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tide-tds/components",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Tide 디자인 시스템의 React Component",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -23,8 +23,8 @@
23
23
  "./styles.css": "./dist/styles.css"
24
24
  },
25
25
  "dependencies": {
26
- "@tide-tds/foundation": "0.4.0",
27
- "@tide-tds/icons": "0.4.0",
26
+ "@tide-tds/foundation": "0.5.1",
27
+ "@tide-tds/icons": "0.5.1",
28
28
  "clsx": "^2.1.1",
29
29
  "react-aria": "^3.53.1",
30
30
  "react-stately": "^3.51.0",