@uniai-fe/uds-primitives 0.6.0 → 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.
package/README.md CHANGED
@@ -53,10 +53,22 @@ export default function Page() {
53
53
  - `Alternate.LoadingDefault`
54
54
  - `Alternate.LoadingIcon`
55
55
  - `Alternate.Text`
56
+ - `Alternate.Layout.Container`
57
+ - `Alternate.Layout.Figure`
58
+ - `Alternate.Layout.Title`
59
+ - `Alternate.Layout.Contents`
60
+ - `Alternate.Layout.TextButton`
61
+ - `Alternate.Layout.Button`
56
62
  - `AlternateEmptyDataProps`
57
63
  - `AlternateLoadingDefaultProps`
58
64
  - `AlternateLoadingIconProps`
59
65
  - `AlternateTextProps`
66
+ - `AlternateLayoutContainerProps`
67
+ - `AlternateLayoutFigureProps`
68
+ - `AlternateLayoutTitleProps`
69
+ - `AlternateLayoutContentsProps`
70
+ - `AlternateLayoutTextButtonProps`
71
+ - `AlternateLayoutButtonProps`
60
72
  - `Badge`
61
73
  - `BadgeProps`
62
74
  - `Chip.Default`
@@ -422,10 +434,10 @@ export default function RootLayout({ children }: { children: ReactNode }) {
422
434
 
423
435
  ```plaintext
424
436
  src/components/{category}/
425
- markup/ // 컴포넌트 구현
426
- types/ // 외부 노출 타입
427
- styles/ // SCSS (foundation 토큰 기반)
428
- hooks/ // 카테고리 전용 훅
437
+ markup|unit|layout/ // 컴포넌트 구현
438
+ types/ // 외부 노출 타입
439
+ styles/ // SCSS (foundation 토큰 기반)
440
+ hooks/ // 카테고리 전용 훅
429
441
  ```
430
442
 
431
443
  - 배럴(`components/{category}/index.tsx`)은 항상 `import "./index.scss"`를 포함합니다.
package/dist/styles.css CHANGED
@@ -1,5 +1,32 @@
1
1
  @charset "UTF-8";
2
2
  :root {
3
+ --alternate-unit-text-color: var(--color-cool-gray-70);
4
+ --alternate-unit-text-font-size: 1.4rem;
5
+ --alternate-unit-text-line-height: 1.5;
6
+ --alternate-unit-gap: var(--spacing-gap-6);
7
+ --alternate-loading-icon-small-size: 2.4rem;
8
+ --alternate-loading-icon-medium-size: 3.6rem;
9
+ --alternate-loading-icon-large-size: 5.2rem;
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;
3
30
  --theme-badge-height-xsmall: var(--theme-size-small-1);
4
31
  --theme-badge-height-small: var(--theme-size-small-2);
5
32
  /* 변경: Figma 2421:619 기준 medium 배지 높이 32px를 추가한다. */
@@ -821,17 +848,17 @@
821
848
  }
822
849
  }
823
850
  .empty-text {
824
- font-size: 1.4rem;
825
- color: var(--color-cool-gray-70);
826
- line-height: 1.5;
851
+ color: var(--alternate-unit-text-color);
852
+ font-size: var(--alternate-unit-text-font-size);
853
+ line-height: var(--alternate-unit-text-line-height);
827
854
  word-break: keep-all;
828
855
  text-align: center;
829
856
  }
830
857
 
831
858
  .alternate-text {
832
- font-size: 1.4rem;
833
- color: var(--color-cool-gray-70);
834
- line-height: 1.5;
859
+ color: var(--alternate-unit-text-color);
860
+ font-size: var(--alternate-unit-text-font-size);
861
+ line-height: var(--alternate-unit-text-line-height);
835
862
  word-break: keep-all;
836
863
  text-align: center;
837
864
  }
@@ -849,25 +876,25 @@
849
876
  }
850
877
  .alternate.is-horizontal.loading .empty-text {
851
878
  transform: translateY(0.2rem);
852
- margin-left: 1rem;
879
+ margin-left: var(--alternate-unit-gap);
853
880
  }
854
881
  .alternate.is-horizontal.loading .alternate-text {
855
882
  transform: translateY(0.2rem);
856
- margin-left: 1rem;
883
+ margin-left: var(--alternate-unit-gap);
857
884
  }
858
885
  .alternate.is-vertical {
859
886
  flex-direction: column;
860
887
  }
861
888
  .alternate.is-vertical .empty-text {
862
- margin-top: 1rem;
889
+ margin-top: var(--alternate-unit-gap);
863
890
  }
864
891
  .alternate.is-vertical .alternate-text {
865
- margin-top: 1rem;
892
+ margin-top: var(--alternate-unit-gap);
866
893
  }
867
894
 
868
895
  .alternate-loading-icon {
869
- width: 2.4rem;
870
- height: 2.4rem;
896
+ width: var(--alternate-loading-icon-small-size);
897
+ height: var(--alternate-loading-icon-small-size);
871
898
  position: relative;
872
899
  margin: 0;
873
900
  display: flex;
@@ -876,18 +903,81 @@
876
903
  animation: alternate-loading-spin 1s linear infinite;
877
904
  }
878
905
  .alternate-loading-icon.is-medium {
879
- width: 3.6rem;
880
- height: 3.6rem;
906
+ width: var(--alternate-loading-icon-medium-size);
907
+ height: var(--alternate-loading-icon-medium-size);
881
908
  }
882
909
  .alternate-loading-icon.is-large {
883
- width: 5.2rem;
884
- height: 5.2rem;
910
+ width: var(--alternate-loading-icon-large-size);
911
+ height: var(--alternate-loading-icon-large-size);
885
912
  }
886
913
  .alternate-loading-icon svg {
887
914
  width: 100%;
888
915
  height: 100%;
889
916
  }
890
917
 
918
+ .alternate-layout {
919
+ display: flex;
920
+ flex-direction: column;
921
+ align-items: center;
922
+ justify-content: center;
923
+ width: 100%;
924
+ min-height: var(--alternate-layout-min-block-size);
925
+ box-sizing: border-box;
926
+ gap: var(--alternate-layout-gap);
927
+ text-align: center;
928
+ }
929
+
930
+ .alternate-layout-figure {
931
+ display: flex;
932
+ align-items: center;
933
+ justify-content: center;
934
+ width: var(--alternate-layout-figure-size);
935
+ height: var(--alternate-layout-figure-size);
936
+ margin: 0;
937
+ }
938
+
939
+ .alternate-layout-title {
940
+ margin: 0;
941
+ color: var(--alternate-layout-title-color);
942
+ font-size: var(--alternate-layout-title-font-size);
943
+ line-height: var(--alternate-layout-title-line-height);
944
+ letter-spacing: var(--alternate-layout-title-letter-spacing);
945
+ font-weight: var(--alternate-layout-title-font-weight);
946
+ word-break: keep-all;
947
+ }
948
+
949
+ .alternate-layout-contents {
950
+ max-width: 48rem;
951
+ margin: 0;
952
+ color: var(--alternate-layout-contents-color);
953
+ font-size: var(--alternate-layout-contents-font-size);
954
+ line-height: var(--alternate-layout-contents-line-height);
955
+ letter-spacing: var(--alternate-layout-contents-letter-spacing);
956
+ font-weight: var(--alternate-layout-contents-font-weight);
957
+ word-break: keep-all;
958
+ }
959
+
960
+ .alternate-layout-text-button {
961
+ display: inline;
962
+ padding: 0;
963
+ border: 0;
964
+ background: transparent;
965
+ color: var(--alternate-layout-text-button-color);
966
+ font: inherit;
967
+ text-decoration: underline;
968
+ text-underline-offset: var(--alternate-layout-text-button-underline-offset);
969
+ cursor: pointer;
970
+ }
971
+
972
+ .alternate-layout-text-button:where(:disabled) {
973
+ cursor: default;
974
+ opacity: 0.4;
975
+ }
976
+
977
+ .alternate-layout-button {
978
+ flex-shrink: 0;
979
+ }
980
+
891
981
  /* Badge 기본 토큰 래핑 */
892
982
 
893
983
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-primitives",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "UNIAI Design System; Primitives Components Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,7 +1,8 @@
1
- import AlternateEmptyData from "./empty/Data";
2
- import AlternateText from "./Label";
3
- import AlternateLoadingDefault from "./loading/Default";
4
- import AlternateLoadingIcon from "./loading/Icon";
1
+ import AlternateLayout from "./layout/Layout";
2
+ import AlternateEmptyData from "./unit/empty/Data";
3
+ import AlternateText from "./unit/Text";
4
+ import AlternateLoadingDefault from "./unit/loading/Default";
5
+ import AlternateLoadingIcon from "./unit/loading/Icon";
5
6
 
6
7
  /**
7
8
  * Alternate; empty/loading fallback namespace
@@ -10,12 +11,14 @@ import AlternateLoadingIcon from "./loading/Icon";
10
11
  * - `Alternate.LoadingDefault`: 로딩 안내 레이아웃이다.
11
12
  * - `Alternate.LoadingIcon`: 단독 스피너 아이콘이다.
12
13
  * - `Alternate.Text`: 안내 문구 슬롯이다.
14
+ * - `Alternate.Layout`: edge-case 조합을 위한 anatomy namespace다.
13
15
  */
14
16
  const Alternate = {
15
17
  EmptyData: AlternateEmptyData,
16
18
  LoadingDefault: AlternateLoadingDefault,
17
19
  LoadingIcon: AlternateLoadingIcon,
18
20
  Text: AlternateText,
21
+ Layout: AlternateLayout,
19
22
  };
20
23
 
21
24
  export default Alternate;
@@ -5,6 +5,7 @@
5
5
  * - `Alternate.LoadingDefault`: 로딩 아이콘과 안내 문구를 함께 렌더링한다.
6
6
  * - `Alternate.LoadingIcon`: 단독 스피너 아이콘이다.
7
7
  * - `Alternate.Text`: fallback 문구 스타일 슬롯이다.
8
+ * - `Alternate.Layout`: edge-case 조합 anatomy다.
8
9
  */
9
- export { default as Alternate } from "./markup";
10
+ export { default as Alternate } from "./Alternate";
10
11
  export type * from "./types";
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import { clsx } from "clsx";
4
+ import { Button } from "../../button";
5
+ import type { AlternateLayoutButtonProps } from "../types";
6
+
7
+ /**
8
+ * Alternate Layout Button; Button.Default preset adapter
9
+ * @component
10
+ * @param {AlternateLayoutButtonProps} props
11
+ * @param {React.ReactNode} [props.children] 버튼 라벨 또는 콘텐츠다.
12
+ * @param {string} [props.className] 버튼 className override다.
13
+ * @example
14
+ * <Alternate.Layout.Button as="a" href="/">홈으로 이동</Alternate.Layout.Button>
15
+ */
16
+ export default function AlternateLayoutButton({
17
+ className,
18
+ ...buttonProps
19
+ }: AlternateLayoutButtonProps) {
20
+ return (
21
+ <Button.Default
22
+ {...buttonProps}
23
+ className={clsx("alternate-layout-button", className)}
24
+ fill="outlined"
25
+ priority="secondary"
26
+ size="small"
27
+ />
28
+ );
29
+ }
@@ -0,0 +1,24 @@
1
+ import { clsx } from "clsx";
2
+ import type { AlternateLayoutContainerProps } from "../types";
3
+
4
+ /**
5
+ * Alternate Layout Container; edge-case anatomy 루트
6
+ * @component
7
+ * @param {AlternateLayoutContainerProps} props
8
+ * @param {string} [props.className] 루트 className override다.
9
+ * @param {React.ReactNode} [props.children] layout 내부 콘텐츠다.
10
+ * @example
11
+ * <Alternate.Layout.Container>
12
+ * <Alternate.Layout.Title>데이터가 없습니다.</Alternate.Layout.Title>
13
+ * </Alternate.Layout.Container>
14
+ */
15
+ export default function AlternateLayoutContainer({
16
+ className,
17
+ children,
18
+ }: AlternateLayoutContainerProps) {
19
+ return (
20
+ <section className={clsx("alternate-layout", className)}>
21
+ {children}
22
+ </section>
23
+ );
24
+ }
@@ -0,0 +1,23 @@
1
+ import { clsx } from "clsx";
2
+ import { Slot } from "../../slot";
3
+ import type { AlternateLayoutContentsProps } from "../types";
4
+
5
+ /**
6
+ * Alternate Layout Contents; edge-case 본문
7
+ * @component
8
+ * @param {AlternateLayoutContentsProps} props
9
+ * @param {string} [props.className] 본문 className override다.
10
+ * @param {React.ReactNode} [props.children] 본문 콘텐츠다.
11
+ * @example
12
+ * <Alternate.Layout.Contents>다시 시도해 주세요.</Alternate.Layout.Contents>
13
+ */
14
+ export default function AlternateLayoutContents({
15
+ className,
16
+ children,
17
+ }: AlternateLayoutContentsProps) {
18
+ return (
19
+ <Slot.Text as="p" className={clsx("alternate-layout-contents", className)}>
20
+ {children}
21
+ </Slot.Text>
22
+ );
23
+ }
@@ -0,0 +1,24 @@
1
+ import { clsx } from "clsx";
2
+ import type { AlternateLayoutFigureProps } from "../types";
3
+
4
+ /**
5
+ * Alternate Layout Figure; edge-case visual 영역
6
+ * @component
7
+ * @param {AlternateLayoutFigureProps} props
8
+ * @param {string} [props.className] figure className override다.
9
+ * @param {React.ReactNode} [props.children] 도입 측이 직접 제어하는 SVG, 이미지, 아이콘 콘텐츠다.
10
+ * @example
11
+ * <Alternate.Layout.Figure>
12
+ * <img src="/empty.svg" alt="" />
13
+ * </Alternate.Layout.Figure>
14
+ */
15
+ export default function AlternateLayoutFigure({
16
+ className,
17
+ children,
18
+ }: AlternateLayoutFigureProps) {
19
+ return (
20
+ <figure className={clsx("alternate-layout-figure", className)}>
21
+ {children}
22
+ </figure>
23
+ );
24
+ }
@@ -0,0 +1,27 @@
1
+ import AlternateLayoutButton from "./Button";
2
+ import AlternateLayoutContainer from "./Container";
3
+ import AlternateLayoutContents from "./Contents";
4
+ import AlternateLayoutFigure from "./Figure";
5
+ import AlternateLayoutTextButton from "./TextButton";
6
+ import AlternateLayoutTitle from "./Title";
7
+
8
+ /**
9
+ * Alternate Layout; edge-case anatomy namespace
10
+ * @desc
11
+ * - `Container`: 화면 상태 조합 루트다.
12
+ * - `Figure`: 도입 측이 제어하는 visual 영역이다.
13
+ * - `Title`: 제목 텍스트 영역이다.
14
+ * - `Contents`: 본문 텍스트 영역이다.
15
+ * - `TextButton`: paragraph 내부 text action이다.
16
+ * - `Button`: Button.Default 기반 action adapter다.
17
+ */
18
+ const AlternateLayout = {
19
+ Container: AlternateLayoutContainer,
20
+ Figure: AlternateLayoutFigure,
21
+ Title: AlternateLayoutTitle,
22
+ Contents: AlternateLayoutContents,
23
+ TextButton: AlternateLayoutTextButton,
24
+ Button: AlternateLayoutButton,
25
+ };
26
+
27
+ export default AlternateLayout;
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import { clsx } from "clsx";
4
+ import type { AlternateLayoutTextButtonProps } from "../types";
5
+
6
+ /**
7
+ * Alternate Layout TextButton; paragraph 내부 text action
8
+ * @component
9
+ * @param {AlternateLayoutTextButtonProps} props
10
+ * @param {string} [props.className] action className override다.
11
+ * @param {React.ReactNode} [props.children] action 라벨 콘텐츠다.
12
+ * @param {string} [props.href] 링크 action으로 렌더링할 href다.
13
+ * @param {string} [props.target] 링크 target 값이다.
14
+ * @example
15
+ * <Alternate.Layout.TextButton onClick={onRetry}>다시 시도</Alternate.Layout.TextButton>
16
+ */
17
+ export default function AlternateLayoutTextButton({
18
+ className,
19
+ children,
20
+ href,
21
+ target,
22
+ type: typeProp,
23
+ ...htmlAttrs
24
+ }: AlternateLayoutTextButtonProps) {
25
+ const textButtonClassName = clsx("alternate-layout-text-button", className);
26
+
27
+ if (href) {
28
+ return (
29
+ <a
30
+ className={textButtonClassName}
31
+ href={href}
32
+ target={target}
33
+ {...htmlAttrs}
34
+ >
35
+ {children}
36
+ </a>
37
+ );
38
+ }
39
+
40
+ // 변경: href가 없으면 문장 내부 action을 native button으로 고정한다.
41
+ return (
42
+ <button
43
+ className={textButtonClassName}
44
+ type={typeProp ?? "button"}
45
+ {...htmlAttrs}
46
+ >
47
+ {children}
48
+ </button>
49
+ );
50
+ }
@@ -0,0 +1,25 @@
1
+ import { clsx } from "clsx";
2
+ import { Slot } from "../../slot";
3
+ import type { AlternateLayoutTitleProps } from "../types";
4
+
5
+ /**
6
+ * Alternate Layout Title; edge-case 제목
7
+ * @component
8
+ * @param {AlternateLayoutTitleProps} props
9
+ * @param {React.ElementType} [props.as="strong"] 렌더링할 제목 요소다.
10
+ * @param {string} [props.className] 제목 className override다.
11
+ * @param {React.ReactNode} [props.children] 제목 콘텐츠다.
12
+ * @example
13
+ * <Alternate.Layout.Title as="h1">페이지를 찾을 수 없습니다.</Alternate.Layout.Title>
14
+ */
15
+ export default function AlternateLayoutTitle({
16
+ as = "strong",
17
+ className,
18
+ children,
19
+ }: AlternateLayoutTitleProps) {
20
+ return (
21
+ <Slot.Text as={as} className={clsx("alternate-layout-title", className)}>
22
+ {children}
23
+ </Slot.Text>
24
+ );
25
+ }
@@ -0,0 +1 @@
1
+ export { default as AlternateLayout } from "./Layout";
@@ -1 +1,3 @@
1
- @forward "./alternate";
1
+ @forward "./variables";
2
+ @forward "./unit";
3
+ @forward "./layout";
@@ -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
  /**