@uniai-fe/uds-primitives 0.5.6 → 0.6.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.
Files changed (42) hide show
  1. package/README.md +25 -4
  2. package/dist/styles.css +237 -16
  3. package/package.json +1 -1
  4. package/src/components/alternate/{markup/index.tsx → Alternate.tsx} +7 -4
  5. package/src/components/alternate/index.tsx +2 -1
  6. package/src/components/alternate/layout/Button.tsx +29 -0
  7. package/src/components/alternate/layout/Container.tsx +24 -0
  8. package/src/components/alternate/layout/Contents.tsx +23 -0
  9. package/src/components/alternate/layout/Figure.tsx +24 -0
  10. package/src/components/alternate/layout/Layout.tsx +27 -0
  11. package/src/components/alternate/layout/TextButton.tsx +50 -0
  12. package/src/components/alternate/layout/Title.tsx +25 -0
  13. package/src/components/alternate/layout/index.tsx +1 -0
  14. package/src/components/alternate/styles/index.scss +3 -1
  15. package/src/components/alternate/styles/layout.scss +63 -0
  16. package/src/components/alternate/styles/{alternate.scss → unit.scss} +16 -16
  17. package/src/components/alternate/styles/variables.scss +30 -0
  18. package/src/components/alternate/types/index.ts +2 -77
  19. package/src/components/alternate/types/layout.ts +126 -0
  20. package/src/components/alternate/types/unit.ts +77 -0
  21. package/src/components/alternate/{markup → unit}/empty/Data.tsx +1 -1
  22. package/src/components/alternate/{markup → unit}/loading/Default.tsx +1 -1
  23. package/src/components/toast/img/error.svg +6 -0
  24. package/src/components/toast/img/success.svg +5 -0
  25. package/src/components/toast/img/warning.svg +5 -0
  26. package/src/components/toast/index.scss +1 -0
  27. package/src/components/toast/index.tsx +11 -0
  28. package/src/components/toast/markup/Host.tsx +74 -0
  29. package/src/components/toast/markup/Icon.tsx +15 -0
  30. package/src/components/toast/markup/Item.tsx +100 -0
  31. package/src/components/toast/markup/Text.tsx +21 -0
  32. package/src/components/toast/markup/index.tsx +16 -0
  33. package/src/components/toast/styles/index.scss +2 -0
  34. package/src/components/toast/styles/toast.scss +113 -0
  35. package/src/components/toast/styles/variables.scss +24 -0
  36. package/src/components/toast/types/index.ts +1 -0
  37. package/src/components/toast/types/internal.ts +71 -0
  38. package/src/components/toast/types/props.ts +128 -0
  39. package/src/index.scss +1 -0
  40. package/src/index.tsx +1 -0
  41. /package/src/components/alternate/{markup/Label.tsx → unit/Text.tsx} +0 -0
  42. /package/src/components/alternate/{markup → unit}/loading/Icon.tsx +0 -0
@@ -0,0 +1,63 @@
1
+ .alternate-layout {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ justify-content: center;
6
+ width: 100%;
7
+ min-height: var(--alternate-layout-min-block-size);
8
+ box-sizing: border-box;
9
+ gap: var(--alternate-layout-gap);
10
+ text-align: center;
11
+ }
12
+
13
+ .alternate-layout-figure {
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ width: var(--alternate-layout-figure-size);
18
+ height: var(--alternate-layout-figure-size);
19
+ margin: 0;
20
+ }
21
+
22
+ .alternate-layout-title {
23
+ margin: 0;
24
+ color: var(--alternate-layout-title-color);
25
+ font-size: var(--alternate-layout-title-font-size);
26
+ line-height: var(--alternate-layout-title-line-height);
27
+ letter-spacing: var(--alternate-layout-title-letter-spacing);
28
+ font-weight: var(--alternate-layout-title-font-weight);
29
+ word-break: keep-all;
30
+ }
31
+
32
+ .alternate-layout-contents {
33
+ max-width: 48rem;
34
+ margin: 0;
35
+ color: var(--alternate-layout-contents-color);
36
+ font-size: var(--alternate-layout-contents-font-size);
37
+ line-height: var(--alternate-layout-contents-line-height);
38
+ letter-spacing: var(--alternate-layout-contents-letter-spacing);
39
+ font-weight: var(--alternate-layout-contents-font-weight);
40
+ word-break: keep-all;
41
+ }
42
+
43
+ .alternate-layout-text-button {
44
+ // 변경: paragraph 내부 text action이라는 명시 계약에 따라 inline display 예외를 둔다.
45
+ display: inline;
46
+ padding: 0;
47
+ border: 0;
48
+ background: transparent;
49
+ color: var(--alternate-layout-text-button-color);
50
+ font: inherit;
51
+ text-decoration: underline;
52
+ text-underline-offset: var(--alternate-layout-text-button-underline-offset);
53
+ cursor: pointer;
54
+ }
55
+
56
+ .alternate-layout-text-button:where(:disabled) {
57
+ cursor: default;
58
+ opacity: 0.4;
59
+ }
60
+
61
+ .alternate-layout-button {
62
+ flex-shrink: 0;
63
+ }
@@ -9,17 +9,17 @@
9
9
  }
10
10
 
11
11
  .empty-text {
12
- font-size: 1.4rem;
13
- color: var(--color-cool-gray-70);
14
- line-height: 1.5;
12
+ color: var(--alternate-unit-text-color);
13
+ font-size: var(--alternate-unit-text-font-size);
14
+ line-height: var(--alternate-unit-text-line-height);
15
15
  word-break: keep-all;
16
16
  text-align: center;
17
17
  }
18
18
 
19
19
  .alternate-text {
20
- font-size: 1.4rem;
21
- color: var(--color-cool-gray-70);
22
- line-height: 1.5;
20
+ color: var(--alternate-unit-text-color);
21
+ font-size: var(--alternate-unit-text-font-size);
22
+ line-height: var(--alternate-unit-text-line-height);
23
23
  word-break: keep-all;
24
24
  text-align: center;
25
25
  }
@@ -38,11 +38,11 @@
38
38
  &.loading {
39
39
  .empty-text {
40
40
  transform: translateY(0.2rem);
41
- margin-left: 1rem;
41
+ margin-left: var(--alternate-unit-gap);
42
42
  }
43
43
  .alternate-text {
44
44
  transform: translateY(0.2rem);
45
- margin-left: 1rem;
45
+ margin-left: var(--alternate-unit-gap);
46
46
  }
47
47
  }
48
48
  }
@@ -50,17 +50,17 @@
50
50
  &.is-vertical {
51
51
  flex-direction: column;
52
52
  .empty-text {
53
- margin-top: 1rem;
53
+ margin-top: var(--alternate-unit-gap);
54
54
  }
55
55
  .alternate-text {
56
- margin-top: 1rem;
56
+ margin-top: var(--alternate-unit-gap);
57
57
  }
58
58
  }
59
59
  }
60
60
 
61
61
  .alternate-loading-icon {
62
- width: 2.4rem;
63
- height: 2.4rem;
62
+ width: var(--alternate-loading-icon-small-size);
63
+ height: var(--alternate-loading-icon-small-size);
64
64
  position: relative;
65
65
  margin: 0;
66
66
  display: flex;
@@ -69,13 +69,13 @@
69
69
  animation: alternate-loading-spin 1s linear infinite;
70
70
 
71
71
  &.is-medium {
72
- width: 3.6rem;
73
- height: 3.6rem;
72
+ width: var(--alternate-loading-icon-medium-size);
73
+ height: var(--alternate-loading-icon-medium-size);
74
74
  }
75
75
 
76
76
  &.is-large {
77
- width: 5.2rem;
78
- height: 5.2rem;
77
+ width: var(--alternate-loading-icon-large-size);
78
+ height: var(--alternate-loading-icon-large-size);
79
79
  }
80
80
 
81
81
  svg {
@@ -0,0 +1,30 @@
1
+ :root {
2
+ --alternate-unit-text-color: var(--color-cool-gray-70);
3
+ --alternate-unit-text-font-size: 1.4rem;
4
+ --alternate-unit-text-line-height: 1.5;
5
+ --alternate-unit-gap: var(--spacing-gap-6);
6
+ --alternate-loading-icon-small-size: 2.4rem;
7
+ --alternate-loading-icon-medium-size: 3.6rem;
8
+ --alternate-loading-icon-large-size: 5.2rem;
9
+
10
+ --alternate-layout-min-block-size: 320px;
11
+ --alternate-layout-gap: var(--spacing-gap-7);
12
+ --alternate-layout-content-gap: var(--spacing-gap-4);
13
+ --alternate-layout-figure-size: 120px;
14
+ --alternate-layout-title-color: var(--color-label-strong);
15
+ --alternate-layout-title-font-size: var(--font-heading-small-size);
16
+ --alternate-layout-title-line-height: var(--font-heading-small-line-height);
17
+ --alternate-layout-title-letter-spacing: var(
18
+ --font-heading-small-letter-spacing
19
+ );
20
+ --alternate-layout-title-font-weight: var(--font-heading-small-weight);
21
+ --alternate-layout-contents-color: var(--color-label-neutral);
22
+ --alternate-layout-contents-font-size: var(--font-body-small-size);
23
+ --alternate-layout-contents-line-height: var(--font-body-small-line-height);
24
+ --alternate-layout-contents-letter-spacing: var(
25
+ --font-body-small-letter-spacing
26
+ );
27
+ --alternate-layout-contents-font-weight: var(--font-body-small-weight);
28
+ --alternate-layout-text-button-color: var(--color-primary-standard);
29
+ --alternate-layout-text-button-underline-offset: 0.2em;
30
+ }
@@ -1,77 +1,2 @@
1
- import type { ReactNode } from "react";
2
-
3
- /**
4
- * Alternate Direction; fallback 레이아웃 방향 축
5
- * @typedef {"vertical" | "horizontal"} AlternateDirection
6
- */
7
- export type AlternateDirection = "vertical" | "horizontal";
8
-
9
- /**
10
- * Alternate Loading Icon Size; 로딩 아이콘 스케일 축
11
- * @typedef {"small" | "medium" | "large"} AlternateLoadingIconSize
12
- */
13
- export type AlternateLoadingIconSize = "small" | "medium" | "large";
14
-
15
- /**
16
- * Alternate Text Props; fallback 문구 슬롯 props
17
- * @property {ReactNode} children 렌더링할 문구 콘텐츠다.
18
- */
19
- export interface AlternateTextProps {
20
- /**
21
- * 렌더링할 문구 콘텐츠다.
22
- */
23
- children: ReactNode;
24
- }
25
-
26
- /**
27
- * Alternate Empty Data Props; 빈 데이터 안내 레이아웃 props
28
- * @property {string} [className] 루트 className override다.
29
- * @property {"vertical" | "horizontal"} [direction] 아이콘/문구 배치 방향이다.
30
- * @property {ReactNode} [children] 안내 문구 콘텐츠다.
31
- */
32
- export interface AlternateEmptyDataProps {
33
- /**
34
- * 루트 className override다.
35
- */
36
- className?: string;
37
- /**
38
- * 아이콘/문구 배치 방향이다.
39
- */
40
- direction?: AlternateDirection;
41
- /**
42
- * 안내 문구 콘텐츠다.
43
- */
44
- children?: ReactNode;
45
- }
46
-
47
- /**
48
- * Alternate Loading Default Props; 로딩 안내 레이아웃 props
49
- * @property {"vertical" | "horizontal"} [direction] 아이콘/문구 배치 방향이다.
50
- * @property {ReactNode} [children] 안내 문구 콘텐츠다.
51
- */
52
- export interface AlternateLoadingDefaultProps {
53
- /**
54
- * 아이콘/문구 배치 방향이다.
55
- */
56
- direction?: AlternateDirection;
57
- /**
58
- * 안내 문구 콘텐츠다.
59
- */
60
- children?: ReactNode;
61
- }
62
-
63
- /**
64
- * Alternate Loading Icon Props; 단독 스피너 아이콘 props
65
- * @property {"small" | "medium" | "large"} [size] 로딩 아이콘 스케일이다.
66
- * @property {"vertical" | "horizontal"} [direction] 부모 레이아웃 방향 클래스 동기화용 값이다.
67
- */
68
- export interface AlternateLoadingIconProps {
69
- /**
70
- * 로딩 아이콘 스케일이다.
71
- */
72
- size?: AlternateLoadingIconSize;
73
- /**
74
- * 부모 레이아웃 방향 클래스 동기화용 값이다.
75
- */
76
- direction?: AlternateDirection;
77
- }
1
+ export type * from "./unit";
2
+ export type * from "./layout";
@@ -0,0 +1,126 @@
1
+ import type { ComponentPropsWithoutRef, ElementType, ReactNode } from "react";
2
+ import type { ButtonProps } from "../../button/types";
3
+
4
+ /**
5
+ * Alternate Layout Container Props; edge-case anatomy 루트 props
6
+ * @property {string} [className] 루트 className override다.
7
+ * @property {ReactNode} [children] layout 내부 콘텐츠다.
8
+ */
9
+ export interface AlternateLayoutContainerProps {
10
+ /**
11
+ * 루트 className override다.
12
+ */
13
+ className?: string;
14
+ /**
15
+ * layout 내부 콘텐츠다.
16
+ */
17
+ children?: ReactNode;
18
+ }
19
+
20
+ /**
21
+ * Alternate Layout Figure Props; figure 영역 props
22
+ * @property {string} [className] figure className override다.
23
+ * @property {ReactNode} [children] 도입 측이 직접 제어하는 SVG, 이미지, 아이콘 콘텐츠다.
24
+ */
25
+ export interface AlternateLayoutFigureProps {
26
+ /**
27
+ * figure className override다.
28
+ */
29
+ className?: string;
30
+ /**
31
+ * 도입 측이 직접 제어하는 SVG, 이미지, 아이콘 콘텐츠다.
32
+ */
33
+ children?: ReactNode;
34
+ }
35
+
36
+ /**
37
+ * Alternate Layout Title Props; 제목 텍스트 props
38
+ * @property {ElementType} [as] 렌더링할 제목 요소다. 기본값은 strong이다.
39
+ * @property {string} [className] 제목 className override다.
40
+ * @property {ReactNode} [children] 제목 콘텐츠다.
41
+ */
42
+ export interface AlternateLayoutTitleProps {
43
+ /**
44
+ * 렌더링할 제목 요소다.
45
+ * 기본값은 strong이다.
46
+ */
47
+ as?: ElementType;
48
+ /**
49
+ * 제목 className override다.
50
+ */
51
+ className?: string;
52
+ /**
53
+ * 제목 콘텐츠다.
54
+ */
55
+ children?: ReactNode;
56
+ }
57
+
58
+ /**
59
+ * Alternate Layout Contents Props; 본문 텍스트 props
60
+ * @property {string} [className] 본문 className override다.
61
+ * @property {ReactNode} [children] 본문 콘텐츠다.
62
+ */
63
+ export interface AlternateLayoutContentsProps {
64
+ /**
65
+ * 본문 className override다.
66
+ */
67
+ className?: string;
68
+ /**
69
+ * 본문 콘텐츠다.
70
+ */
71
+ children?: ReactNode;
72
+ }
73
+
74
+ /**
75
+ * Alternate Layout Text Button Native Props; 문장 내부 action native props
76
+ */
77
+ type AlternateLayoutTextButtonNativeProps = Omit<
78
+ ComponentPropsWithoutRef<"button">,
79
+ "children" | "className"
80
+ > &
81
+ Omit<ComponentPropsWithoutRef<"a">, "children" | "className">;
82
+
83
+ /**
84
+ * Alternate Layout Text Button Props; 문장 내부 action props
85
+ * @property {string} [className] action className override다.
86
+ * @property {ReactNode} [children] action 라벨 콘텐츠다.
87
+ * @property {string} [href] 링크 action으로 렌더링할 href다.
88
+ * @property {string} [target] 링크 target 값이다.
89
+ */
90
+ export interface AlternateLayoutTextButtonProps extends AlternateLayoutTextButtonNativeProps {
91
+ /**
92
+ * action className override다.
93
+ */
94
+ className?: string;
95
+ /**
96
+ * action 라벨 콘텐츠다.
97
+ */
98
+ children?: ReactNode;
99
+ /**
100
+ * 링크 action으로 렌더링할 href다.
101
+ */
102
+ href?: string;
103
+ /**
104
+ * 링크 target 값이다.
105
+ */
106
+ target?: string;
107
+ }
108
+
109
+ /**
110
+ * Alternate Layout Button Props; Button.Default adapter props
111
+ * @extends ButtonProps
112
+ * @property {ElementType} [as] 렌더링할 요소다.
113
+ * @property {ReactNode} [children] 버튼 라벨 또는 콘텐츠다.
114
+ * @property {string} [className] 버튼 className override다.
115
+ * @property {"default" | "readonly" | "disabled"} [state] UI 상태다.
116
+ * @property {boolean} [block] width:100% 확장 여부다.
117
+ * @property {"full" | "fit" | "fill" | "auto" | number | string} [width] Form.Field width preset 또는 custom width다.
118
+ * @property {boolean} [loading] true면 readonly 상태로 렌더링한다.
119
+ * @property {ReactNode} [left] 라벨 왼쪽 콘텐츠다.
120
+ * @property {ReactNode} [right] 라벨 오른쪽 콘텐츠다.
121
+ * @property {"hover" | "pressed"} ["data-user-action"] Interaction 상태 강제 표현용 data attr이다.
122
+ */
123
+ export interface AlternateLayoutButtonProps extends Omit<
124
+ ButtonProps,
125
+ "fill" | "priority" | "size" | "scale"
126
+ > {}
@@ -0,0 +1,77 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ /**
4
+ * Alternate Direction; fallback 레이아웃 방향 축
5
+ * @typedef {"vertical" | "horizontal"} AlternateDirection
6
+ */
7
+ export type AlternateDirection = "vertical" | "horizontal";
8
+
9
+ /**
10
+ * Alternate Loading Icon Size; 로딩 아이콘 스케일 축
11
+ * @typedef {"small" | "medium" | "large"} AlternateLoadingIconSize
12
+ */
13
+ export type AlternateLoadingIconSize = "small" | "medium" | "large";
14
+
15
+ /**
16
+ * Alternate Text Props; fallback 문구 슬롯 props
17
+ * @property {ReactNode} children 렌더링할 문구 콘텐츠다.
18
+ */
19
+ export interface AlternateTextProps {
20
+ /**
21
+ * 렌더링할 문구 콘텐츠다.
22
+ */
23
+ children: ReactNode;
24
+ }
25
+
26
+ /**
27
+ * Alternate Empty Data Props; 빈 데이터 안내 레이아웃 props
28
+ * @property {string} [className] 루트 className override다.
29
+ * @property {"vertical" | "horizontal"} [direction] 아이콘/문구 배치 방향이다.
30
+ * @property {ReactNode} [children] 안내 문구 콘텐츠다.
31
+ */
32
+ export interface AlternateEmptyDataProps {
33
+ /**
34
+ * 루트 className override다.
35
+ */
36
+ className?: string;
37
+ /**
38
+ * 아이콘/문구 배치 방향이다.
39
+ */
40
+ direction?: AlternateDirection;
41
+ /**
42
+ * 안내 문구 콘텐츠다.
43
+ */
44
+ children?: ReactNode;
45
+ }
46
+
47
+ /**
48
+ * Alternate Loading Default Props; 로딩 안내 레이아웃 props
49
+ * @property {"vertical" | "horizontal"} [direction] 아이콘/문구 배치 방향이다.
50
+ * @property {ReactNode} [children] 안내 문구 콘텐츠다.
51
+ */
52
+ export interface AlternateLoadingDefaultProps {
53
+ /**
54
+ * 아이콘/문구 배치 방향이다.
55
+ */
56
+ direction?: AlternateDirection;
57
+ /**
58
+ * 안내 문구 콘텐츠다.
59
+ */
60
+ children?: ReactNode;
61
+ }
62
+
63
+ /**
64
+ * Alternate Loading Icon Props; 단독 스피너 아이콘 props
65
+ * @property {"small" | "medium" | "large"} [size] 로딩 아이콘 스케일이다.
66
+ * @property {"vertical" | "horizontal"} [direction] 부모 레이아웃 방향 클래스 동기화용 값이다.
67
+ */
68
+ export interface AlternateLoadingIconProps {
69
+ /**
70
+ * 로딩 아이콘 스케일이다.
71
+ */
72
+ size?: AlternateLoadingIconSize;
73
+ /**
74
+ * 부모 레이아웃 방향 클래스 동기화용 값이다.
75
+ */
76
+ direction?: AlternateDirection;
77
+ }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { clsx } from "clsx";
4
- import AlternateText from "../Label";
4
+ import AlternateText from "../Text";
5
5
  import type { AlternateEmptyDataProps } from "../../types";
6
6
 
7
7
  /**
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { clsx } from "clsx";
4
4
  import AlternateLoadingIcon from "./Icon";
5
- import AlternateText from "../Label";
5
+ import AlternateText from "../Text";
6
6
  import type { AlternateLoadingDefaultProps } from "../../types";
7
7
 
8
8
  /**
@@ -0,0 +1,6 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <circle cx="12" cy="12" r="10" fill="#313235"/>
3
+ <circle cx="12" cy="12" r="10" fill="#DA1D0B"/>
4
+ <path d="M12 15C12.5523 15 13 15.4477 13 16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16C11 15.4477 11.4477 15 12 15Z" fill="white"/>
5
+ <path d="M12 7.2002C12.4418 7.2002 12.7998 7.55817 12.7998 8V13C12.7998 13.4418 12.4418 13.7998 12 13.7998C11.5582 13.7998 11.2002 13.4418 11.2002 13V8C11.2002 7.55817 11.5582 7.2002 12 7.2002Z" fill="white"/>
6
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <circle cx="12" cy="12" r="10" fill="#313235"/>
3
+ <circle cx="12" cy="12" r="10" fill="#1AB24D"/>
4
+ <path d="M7.19152 11.8688C6.8791 11.5564 6.8791 11.0501 7.19152 10.7377C7.50394 10.4253 8.0102 10.4253 8.32262 10.7377L10.9391 13.3541L15.6768 8.61636C15.9892 8.30394 16.4955 8.30394 16.8079 8.61636C17.1203 8.92877 17.1203 9.43503 16.8079 9.74745L11.5046 15.0508C11.1922 15.3632 10.6859 15.3632 10.3735 15.0508L7.19152 11.8688Z" fill="white"/>
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M2.6303 18.0362L10.2478 4.18631C11.0076 2.80476 12.9928 2.80476 13.7526 4.18631L21.3701 18.0362C22.1032 19.3691 21.1389 21.0001 19.6176 21.0001H4.38273C2.86153 21.0001 1.8972 19.3691 2.6303 18.0362Z" fill="#F2CC0D"/>
3
+ <path d="M12 16C12.5523 16 13 16.4477 13 17C13 17.5523 12.5523 18 12 18C11.4477 18 11 17.5523 11 17C11 16.4477 11.4477 16 12 16Z" fill="#313235"/>
4
+ <path d="M12 8.2002C12.4418 8.2002 12.7998 8.55817 12.7998 9V14C12.7998 14.4418 12.4418 14.7998 12 14.7998C11.5582 14.7998 11.2002 14.4418 11.2002 14V9C11.2002 8.55817 11.5582 8.2002 12 8.2002Z" fill="#313235"/>
5
+ </svg>
@@ -0,0 +1 @@
1
+ @use "./styles";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Toast; semantic feedback toast 카테고리 배럴
3
+ * @desc
4
+ * - `Toast.Host`: 트리거 로직이 만든 item data를 위치별 stack으로 렌더링한다.
5
+ * - `Toast.Item`: state icon/content/timer close를 담당하는 개별 toast다.
6
+ * - `ToastIcon`, `ToastItemData`, `ToastState`: public contract 도구다.
7
+ */
8
+ import "./index.scss";
9
+
10
+ export * from "./markup";
11
+ export type * from "./types";
@@ -0,0 +1,74 @@
1
+ import { Fragment } from "react";
2
+
3
+ import type { ToastHostProps, ToastItemData } from "../types";
4
+ import type { ToastStackGroup, ToastStackStyle } from "../types/internal";
5
+ import { ToastItem } from "./Item";
6
+
7
+ /**
8
+ * Toast Host; x/y/margin 기준으로 Toast stack을 렌더링하는 Host
9
+ * @component
10
+ * @param {ToastHostProps} props
11
+ * @param {ToastItemData[]} props.items Toast item 데이터 목록
12
+ * @param {(toastKey: string) => void} props.onClose 닫힘 요청 핸들러
13
+ * @example
14
+ * <Toast.Host items={toastItems} onClose={onToastClose} />
15
+ */
16
+ export function ToastHost({ items, onClose }: ToastHostProps) {
17
+ const stackGroups = items.reduce<ToastStackGroup[]>((groups, item) => {
18
+ const stackKey = `${item.y}-${item.x}-${String(item.margin)}`;
19
+ const nextGroup = groups.find(group => group.stackKey === stackKey);
20
+
21
+ if (nextGroup) {
22
+ nextGroup.items.push(item);
23
+ return groups;
24
+ }
25
+
26
+ groups.push({
27
+ stackKey,
28
+ x: item.x,
29
+ y: item.y,
30
+ margin: item.margin,
31
+ items: [item],
32
+ });
33
+
34
+ return groups;
35
+ }, []);
36
+
37
+ if (!stackGroups.length) {
38
+ return null;
39
+ }
40
+
41
+ return (
42
+ <Fragment>
43
+ {stackGroups.map(group => {
44
+ const stackMargin =
45
+ typeof group.margin === "number" ? `${group.margin}px` : group.margin;
46
+ const stackStyle: ToastStackStyle = {
47
+ "--toast-stack-margin": stackMargin,
48
+ };
49
+
50
+ return (
51
+ <div
52
+ key={group.stackKey}
53
+ className="toast-stack"
54
+ data-x={group.x}
55
+ data-y={group.y}
56
+ style={stackStyle}
57
+ >
58
+ {group.items.map((item: ToastItemData) => (
59
+ <ToastItem
60
+ key={item.toastKey}
61
+ toastKey={item.toastKey}
62
+ duration={item.duration}
63
+ state={item.state}
64
+ message={item.message}
65
+ description={item.description}
66
+ onClose={onClose}
67
+ />
68
+ ))}
69
+ </div>
70
+ );
71
+ })}
72
+ </Fragment>
73
+ );
74
+ }
@@ -0,0 +1,15 @@
1
+ import ErrorIcon from "../img/error.svg";
2
+ import SuccessIcon from "../img/success.svg";
3
+ import WarningIcon from "../img/warning.svg";
4
+
5
+ /**
6
+ * Toast Icon Set; 상태별 아이콘 컴포넌트 맵
7
+ * @desc
8
+ * - standard는 Figma 기준 아이콘을 렌더링하지 않는다.
9
+ * - success/warning/error는 제공된 state SVG를 사용한다.
10
+ */
11
+ export const ToastIcon = {
12
+ success: SuccessIcon,
13
+ warning: WarningIcon,
14
+ error: ErrorIcon,
15
+ } as const;