@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.
- package/README.md +25 -4
- package/dist/styles.css +237 -16
- package/package.json +1 -1
- package/src/components/alternate/{markup/index.tsx → Alternate.tsx} +7 -4
- package/src/components/alternate/index.tsx +2 -1
- package/src/components/alternate/layout/Button.tsx +29 -0
- package/src/components/alternate/layout/Container.tsx +24 -0
- package/src/components/alternate/layout/Contents.tsx +23 -0
- package/src/components/alternate/layout/Figure.tsx +24 -0
- package/src/components/alternate/layout/Layout.tsx +27 -0
- package/src/components/alternate/layout/TextButton.tsx +50 -0
- package/src/components/alternate/layout/Title.tsx +25 -0
- package/src/components/alternate/layout/index.tsx +1 -0
- package/src/components/alternate/styles/index.scss +3 -1
- package/src/components/alternate/styles/layout.scss +63 -0
- package/src/components/alternate/styles/{alternate.scss → unit.scss} +16 -16
- package/src/components/alternate/styles/variables.scss +30 -0
- package/src/components/alternate/types/index.ts +2 -77
- package/src/components/alternate/types/layout.ts +126 -0
- package/src/components/alternate/types/unit.ts +77 -0
- package/src/components/alternate/{markup → unit}/empty/Data.tsx +1 -1
- package/src/components/alternate/{markup → unit}/loading/Default.tsx +1 -1
- package/src/components/toast/img/error.svg +6 -0
- package/src/components/toast/img/success.svg +5 -0
- package/src/components/toast/img/warning.svg +5 -0
- package/src/components/toast/index.scss +1 -0
- package/src/components/toast/index.tsx +11 -0
- package/src/components/toast/markup/Host.tsx +74 -0
- package/src/components/toast/markup/Icon.tsx +15 -0
- package/src/components/toast/markup/Item.tsx +100 -0
- package/src/components/toast/markup/Text.tsx +21 -0
- package/src/components/toast/markup/index.tsx +16 -0
- package/src/components/toast/styles/index.scss +2 -0
- package/src/components/toast/styles/toast.scss +113 -0
- package/src/components/toast/styles/variables.scss +24 -0
- package/src/components/toast/types/index.ts +1 -0
- package/src/components/toast/types/internal.ts +71 -0
- package/src/components/toast/types/props.ts +128 -0
- package/src/index.scss +1 -0
- package/src/index.tsx +1 -0
- /package/src/components/alternate/{markup/Label.tsx → unit/Text.tsx} +0 -0
- /package/src/components/alternate/{markup → unit}/loading/Icon.tsx +0 -0
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`
|
|
@@ -194,6 +206,15 @@ export default function Page() {
|
|
|
194
206
|
- `TableRootProps`
|
|
195
207
|
- `TableContainerProps`
|
|
196
208
|
- `TableCellProps`
|
|
209
|
+
- `Toast.Host`
|
|
210
|
+
- `Toast.Item`
|
|
211
|
+
- `ToastIcon`
|
|
212
|
+
- `ToastItemData`
|
|
213
|
+
- `ToastHostProps`
|
|
214
|
+
- `ToastItemProps`
|
|
215
|
+
- `ToastState`
|
|
216
|
+
- `ToastHorizontal`
|
|
217
|
+
- `ToastVertical`
|
|
197
218
|
- `Button.Default`
|
|
198
219
|
- `Button.Text`
|
|
199
220
|
- `Button.Rounded`
|
|
@@ -413,10 +434,10 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
413
434
|
|
|
414
435
|
```plaintext
|
|
415
436
|
src/components/{category}/
|
|
416
|
-
markup/
|
|
417
|
-
types/
|
|
418
|
-
styles/
|
|
419
|
-
hooks/
|
|
437
|
+
markup|unit|layout/ // 컴포넌트 구현
|
|
438
|
+
types/ // 외부 노출 타입
|
|
439
|
+
styles/ // SCSS (foundation 토큰 기반)
|
|
440
|
+
hooks/ // 카테고리 전용 훅
|
|
420
441
|
```
|
|
421
442
|
|
|
422
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를 추가한다. */
|
|
@@ -779,6 +806,26 @@
|
|
|
779
806
|
--table-td-text-size: 15px;
|
|
780
807
|
--table-td-text-weight: var(--font-body-xsmall-weight);
|
|
781
808
|
--table-td-text-line-height: var(--font-body-xsmall-line-height);
|
|
809
|
+
--toast-stack-margin: var(--spacing-padding-7);
|
|
810
|
+
--toast-stack-gap: var(--spacing-gap-3);
|
|
811
|
+
--toast-stack-z-index: 10000;
|
|
812
|
+
--toast-width: 361px;
|
|
813
|
+
--toast-background-color: var(--color-surface-heavy);
|
|
814
|
+
--toast-foreground-color: var(--color-common-100);
|
|
815
|
+
--toast-radius: var(--theme-radius-large-1);
|
|
816
|
+
--toast-padding-block: var(--spacing-padding-5);
|
|
817
|
+
--toast-padding-inline: var(--spacing-padding-7);
|
|
818
|
+
--toast-gap: var(--spacing-gap-5);
|
|
819
|
+
--toast-icon-size: 24px;
|
|
820
|
+
--toast-transition-duration: 0.6s;
|
|
821
|
+
--toast-transition-easing: cubic-bezier(0.16, 1, 0.3, 1);
|
|
822
|
+
--toast-transition-distance: -24%;
|
|
823
|
+
--toast-transition-x: 0;
|
|
824
|
+
--toast-transition-y: 0;
|
|
825
|
+
--toast-message-font-size: var(--font-body-xsmall-size);
|
|
826
|
+
--toast-message-line-height: var(--font-body-xsmall-line-height);
|
|
827
|
+
--toast-message-letter-spacing: var(--font-body-xsmall-letter-spacing);
|
|
828
|
+
--toast-message-font-weight: var(--font-body-xsmall-weight);
|
|
782
829
|
--tooltip-message-background: var(--color-cool-gray-20);
|
|
783
830
|
--tooltip-message-foreground: var(--color-common-100);
|
|
784
831
|
--tooltip-message-radius: var(--theme-radius-medium-3);
|
|
@@ -801,17 +848,17 @@
|
|
|
801
848
|
}
|
|
802
849
|
}
|
|
803
850
|
.empty-text {
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
line-height:
|
|
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);
|
|
807
854
|
word-break: keep-all;
|
|
808
855
|
text-align: center;
|
|
809
856
|
}
|
|
810
857
|
|
|
811
858
|
.alternate-text {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
line-height:
|
|
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);
|
|
815
862
|
word-break: keep-all;
|
|
816
863
|
text-align: center;
|
|
817
864
|
}
|
|
@@ -829,25 +876,25 @@
|
|
|
829
876
|
}
|
|
830
877
|
.alternate.is-horizontal.loading .empty-text {
|
|
831
878
|
transform: translateY(0.2rem);
|
|
832
|
-
margin-left:
|
|
879
|
+
margin-left: var(--alternate-unit-gap);
|
|
833
880
|
}
|
|
834
881
|
.alternate.is-horizontal.loading .alternate-text {
|
|
835
882
|
transform: translateY(0.2rem);
|
|
836
|
-
margin-left:
|
|
883
|
+
margin-left: var(--alternate-unit-gap);
|
|
837
884
|
}
|
|
838
885
|
.alternate.is-vertical {
|
|
839
886
|
flex-direction: column;
|
|
840
887
|
}
|
|
841
888
|
.alternate.is-vertical .empty-text {
|
|
842
|
-
margin-top:
|
|
889
|
+
margin-top: var(--alternate-unit-gap);
|
|
843
890
|
}
|
|
844
891
|
.alternate.is-vertical .alternate-text {
|
|
845
|
-
margin-top:
|
|
892
|
+
margin-top: var(--alternate-unit-gap);
|
|
846
893
|
}
|
|
847
894
|
|
|
848
895
|
.alternate-loading-icon {
|
|
849
|
-
width:
|
|
850
|
-
height:
|
|
896
|
+
width: var(--alternate-loading-icon-small-size);
|
|
897
|
+
height: var(--alternate-loading-icon-small-size);
|
|
851
898
|
position: relative;
|
|
852
899
|
margin: 0;
|
|
853
900
|
display: flex;
|
|
@@ -856,18 +903,81 @@
|
|
|
856
903
|
animation: alternate-loading-spin 1s linear infinite;
|
|
857
904
|
}
|
|
858
905
|
.alternate-loading-icon.is-medium {
|
|
859
|
-
width:
|
|
860
|
-
height:
|
|
906
|
+
width: var(--alternate-loading-icon-medium-size);
|
|
907
|
+
height: var(--alternate-loading-icon-medium-size);
|
|
861
908
|
}
|
|
862
909
|
.alternate-loading-icon.is-large {
|
|
863
|
-
width:
|
|
864
|
-
height:
|
|
910
|
+
width: var(--alternate-loading-icon-large-size);
|
|
911
|
+
height: var(--alternate-loading-icon-large-size);
|
|
865
912
|
}
|
|
866
913
|
.alternate-loading-icon svg {
|
|
867
914
|
width: 100%;
|
|
868
915
|
height: 100%;
|
|
869
916
|
}
|
|
870
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
|
+
|
|
871
981
|
/* Badge 기본 토큰 래핑 */
|
|
872
982
|
|
|
873
983
|
|
|
@@ -5097,6 +5207,117 @@ figure.chip {
|
|
|
5097
5207
|
|
|
5098
5208
|
|
|
5099
5209
|
|
|
5210
|
+
.toast-stack {
|
|
5211
|
+
--toast-stack-translate-x: 0;
|
|
5212
|
+
--toast-stack-translate-y: 0;
|
|
5213
|
+
position: fixed;
|
|
5214
|
+
z-index: var(--toast-stack-z-index);
|
|
5215
|
+
display: flex;
|
|
5216
|
+
flex-direction: column;
|
|
5217
|
+
gap: var(--toast-stack-gap);
|
|
5218
|
+
pointer-events: none;
|
|
5219
|
+
transform: translate(var(--toast-stack-translate-x), var(--toast-stack-translate-y));
|
|
5220
|
+
}
|
|
5221
|
+
|
|
5222
|
+
.toast-stack:where([data-x=left]) {
|
|
5223
|
+
left: var(--toast-stack-margin);
|
|
5224
|
+
--toast-transition-x: calc(var(--toast-transition-distance) * -1);
|
|
5225
|
+
}
|
|
5226
|
+
|
|
5227
|
+
.toast-stack:where([data-x=center]) {
|
|
5228
|
+
left: 50%;
|
|
5229
|
+
--toast-stack-translate-x: -50%;
|
|
5230
|
+
--toast-transition-distance: -80%;
|
|
5231
|
+
}
|
|
5232
|
+
|
|
5233
|
+
.toast-stack:where([data-x=right]) {
|
|
5234
|
+
right: var(--toast-stack-margin);
|
|
5235
|
+
--toast-transition-x: var(--toast-transition-distance);
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5238
|
+
.toast-stack:where([data-y=top]) {
|
|
5239
|
+
top: var(--toast-stack-margin);
|
|
5240
|
+
}
|
|
5241
|
+
|
|
5242
|
+
.toast-stack:where([data-y=center]) {
|
|
5243
|
+
top: 50%;
|
|
5244
|
+
--toast-stack-translate-y: -50%;
|
|
5245
|
+
}
|
|
5246
|
+
|
|
5247
|
+
.toast-stack:where([data-y=bottom]) {
|
|
5248
|
+
bottom: var(--toast-stack-margin);
|
|
5249
|
+
}
|
|
5250
|
+
|
|
5251
|
+
.toast-stack:where([data-x=center][data-y=top]) {
|
|
5252
|
+
--toast-transition-y: calc(var(--toast-transition-distance) * -1);
|
|
5253
|
+
}
|
|
5254
|
+
|
|
5255
|
+
.toast-stack:where([data-x=center][data-y=bottom]) {
|
|
5256
|
+
--toast-transition-y: var(--toast-transition-distance);
|
|
5257
|
+
}
|
|
5258
|
+
|
|
5259
|
+
.toast {
|
|
5260
|
+
display: flex;
|
|
5261
|
+
align-items: center;
|
|
5262
|
+
width: min(var(--toast-width), 100vw - var(--toast-stack-margin) * 2);
|
|
5263
|
+
box-sizing: border-box;
|
|
5264
|
+
gap: var(--toast-gap);
|
|
5265
|
+
overflow: hidden;
|
|
5266
|
+
padding: var(--toast-padding-block) var(--toast-padding-inline);
|
|
5267
|
+
border-radius: var(--toast-radius);
|
|
5268
|
+
background-color: var(--toast-background-color);
|
|
5269
|
+
opacity: 0;
|
|
5270
|
+
pointer-events: auto;
|
|
5271
|
+
transform: translate(var(--toast-transition-x), var(--toast-transition-y));
|
|
5272
|
+
transition: opacity var(--toast-transition-duration) var(--toast-transition-easing), transform var(--toast-transition-duration) var(--toast-transition-easing);
|
|
5273
|
+
}
|
|
5274
|
+
|
|
5275
|
+
.toast:where([data-phase=open]) {
|
|
5276
|
+
opacity: 1;
|
|
5277
|
+
transform: translateY(0);
|
|
5278
|
+
}
|
|
5279
|
+
|
|
5280
|
+
.toast-icon {
|
|
5281
|
+
display: flex;
|
|
5282
|
+
align-items: center;
|
|
5283
|
+
justify-content: center;
|
|
5284
|
+
flex-shrink: 0;
|
|
5285
|
+
width: var(--toast-icon-size);
|
|
5286
|
+
height: var(--toast-icon-size);
|
|
5287
|
+
margin: 0;
|
|
5288
|
+
}
|
|
5289
|
+
|
|
5290
|
+
.toast-icon svg {
|
|
5291
|
+
display: block;
|
|
5292
|
+
width: 100%;
|
|
5293
|
+
height: 100%;
|
|
5294
|
+
}
|
|
5295
|
+
|
|
5296
|
+
.toast-content {
|
|
5297
|
+
display: flex;
|
|
5298
|
+
flex: 1;
|
|
5299
|
+
min-width: 0;
|
|
5300
|
+
flex-direction: column;
|
|
5301
|
+
}
|
|
5302
|
+
|
|
5303
|
+
.toast-text {
|
|
5304
|
+
overflow: hidden;
|
|
5305
|
+
margin: 0;
|
|
5306
|
+
color: var(--toast-foreground-color);
|
|
5307
|
+
font-size: var(--toast-message-font-size);
|
|
5308
|
+
line-height: var(--toast-message-line-height);
|
|
5309
|
+
letter-spacing: var(--toast-message-letter-spacing);
|
|
5310
|
+
font-weight: var(--toast-message-font-weight);
|
|
5311
|
+
text-overflow: ellipsis;
|
|
5312
|
+
white-space: nowrap;
|
|
5313
|
+
}
|
|
5314
|
+
|
|
5315
|
+
.toast-description {
|
|
5316
|
+
opacity: 0.82;
|
|
5317
|
+
}
|
|
5318
|
+
|
|
5319
|
+
|
|
5320
|
+
|
|
5100
5321
|
.tooltip-trigger {
|
|
5101
5322
|
display: inline-flex;
|
|
5102
5323
|
align-items: center;
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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 "./
|
|
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";
|