@uniai-fe/uds-templates 0.1.16 → 0.1.18

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 (93) hide show
  1. package/dist/styles.css +139 -10
  2. package/package.json +18 -11
  3. package/src/auth/common/complete/Template.tsx +3 -0
  4. package/src/auth/common/complete/types.ts +4 -1
  5. package/src/auth/common/find/markup/CodeStep.tsx +2 -1
  6. package/src/auth/common/find/markup/InfoStep.tsx +2 -1
  7. package/src/auth/common/find/styles/result.scss +1 -1
  8. package/src/auth/find-password/markup/StepResetPassword.tsx +2 -1
  9. package/src/auth/login/markup/FormField.tsx +2 -1
  10. package/src/auth/signup/markup/AccountForm.tsx +2 -1
  11. package/src/auth/signup/markup/Complete.tsx +2 -1
  12. package/src/auth/signup/markup/UserInfoForm.tsx +2 -1
  13. package/src/auth/signup/markup/VerificationForm.tsx +4 -2
  14. package/src/index.scss +1 -0
  15. package/src/index.tsx +1 -0
  16. package/src/modal/core/components/FooterButtons.tsx +4 -1
  17. package/src/modal/templates/Dialog.tsx +4 -2
  18. package/src/modal/types/footer.ts +4 -2
  19. package/src/page-frame/container/index.scss +14 -8
  20. package/src/page-frame/desktop/components/header/Container.tsx +22 -0
  21. package/src/page-frame/desktop/components/header/Section.tsx +20 -0
  22. package/src/page-frame/desktop/components/header/index.tsx +19 -0
  23. package/src/page-frame/desktop/components/header/util/Button.tsx +23 -0
  24. package/src/page-frame/desktop/components/header/util/Container.tsx +18 -0
  25. package/src/page-frame/desktop/components/header/util/Item.tsx +13 -0
  26. package/src/page-frame/desktop/components/header/util/Logout.tsx +26 -0
  27. package/src/page-frame/desktop/components/header/util/setting/Button.tsx +39 -0
  28. package/src/page-frame/desktop/components/index.tsx +15 -0
  29. package/src/page-frame/desktop/components/nav/Button.tsx +24 -0
  30. package/src/page-frame/desktop/components/nav/Container.tsx +70 -0
  31. package/src/page-frame/desktop/components/nav/Logo.tsx +46 -0
  32. package/src/page-frame/desktop/components/nav/index.tsx +5 -0
  33. package/src/page-frame/desktop/components/page/Container.tsx +13 -0
  34. package/src/page-frame/desktop/components/page/ServiceFrame.tsx +15 -0
  35. package/src/page-frame/desktop/components/page/ServiceMain.tsx +13 -0
  36. package/src/page-frame/desktop/components/page/ServiceMainWrapper.tsx +24 -0
  37. package/src/page-frame/desktop/components/page/index.tsx +14 -0
  38. package/src/page-frame/desktop/components/popup/Container.tsx +18 -0
  39. package/src/page-frame/desktop/components/popup/frame/Body.tsx +14 -0
  40. package/src/page-frame/desktop/components/popup/frame/Container.tsx +16 -0
  41. package/src/page-frame/desktop/components/popup/frame/Header.tsx +26 -0
  42. package/src/page-frame/desktop/components/popup/frame/index.tsx +11 -0
  43. package/src/page-frame/desktop/components/popup/index.tsx +9 -0
  44. package/src/page-frame/desktop/index.scss +5 -0
  45. package/src/page-frame/desktop/index.tsx +7 -0
  46. package/src/page-frame/desktop/styles/page/common.scss +40 -0
  47. package/src/page-frame/desktop/styles/page/header.scss +49 -0
  48. package/src/page-frame/desktop/styles/page/nav.scss +128 -0
  49. package/src/page-frame/desktop/styles/popup/popup.scss +45 -0
  50. package/src/page-frame/desktop/styles/variables.scss +28 -0
  51. package/src/page-frame/desktop/types/index.ts +1 -0
  52. package/src/page-frame/desktop/types/nav.ts +77 -0
  53. package/src/page-frame/index.tsx +2 -0
  54. package/src/page-frame/mobile/index.scss +1 -1
  55. package/src/page-frame/types/index.ts +69 -0
  56. package/src/weather/apis/index.ts +4 -0
  57. package/src/weather/apis/korea/client.ts +93 -0
  58. package/src/weather/apis/korea/server.ts +253 -0
  59. package/src/weather/apis/open-weather-map/client.ts +69 -0
  60. package/src/weather/apis/open-weather-map/server.ts +134 -0
  61. package/src/weather/components/icon/Address.tsx +13 -0
  62. package/src/weather/components/icon/Weather.tsx +32 -0
  63. package/src/weather/components/index.tsx +7 -0
  64. package/src/weather/components/page-header/Address.tsx +21 -0
  65. package/src/weather/components/page-header/Alert.tsx +42 -0
  66. package/src/weather/components/page-header/Container.tsx +14 -0
  67. package/src/weather/components/page-header/Forecast.tsx +26 -0
  68. package/src/weather/components/page-header/NextDays.tsx +32 -0
  69. package/src/weather/components/page-header/Today.tsx +135 -0
  70. package/src/weather/context/mock.tsx +45 -0
  71. package/src/weather/data/alert-regions-meta.json +1286 -0
  72. package/src/weather/data/response.ts +36 -0
  73. package/src/weather/data/weather-regions-meta.json +9833 -0
  74. package/src/weather/hooks/index.ts +4 -0
  75. package/src/weather/hooks/useOpenWeatherMap.ts +42 -0
  76. package/src/weather/hooks/useWeatherKorea.ts +78 -0
  77. package/src/weather/hooks/useWeatherKoreaAlert.ts +36 -0
  78. package/src/weather/index.tsx +11 -0
  79. package/src/weather/jotai/coordinate.ts +16 -0
  80. package/src/weather/jotai/farm-idx.ts +5 -0
  81. package/src/weather/jotai/index.ts +2 -0
  82. package/src/weather/styles/weather.scss +151 -0
  83. package/src/weather/types/api.ts +215 -0
  84. package/src/weather/types/base.ts +50 -0
  85. package/src/weather/types/index.ts +4 -0
  86. package/src/weather/types/korea.ts +228 -0
  87. package/src/weather/types/open-weather-map.ts +164 -0
  88. package/src/weather/utils/alert.ts +30 -0
  89. package/src/weather/utils/date-time.ts +65 -0
  90. package/src/weather/utils/index.ts +4 -0
  91. package/src/weather/utils/location.ts +161 -0
  92. package/src/weather/utils/validate.ts +9 -0
  93. package/src/weather/utils/weather.ts +304 -0
package/dist/styles.css CHANGED
@@ -4,10 +4,14 @@
4
4
  --frame-device-width: 480px;
5
5
  --frame-safe-area-top: env(safe-area-inset-top, 0px);
6
6
  --frame-safe-area-bottom: env(safe-area-inset-bottom, 0px);
7
- --page-frame-height: min(100svh, 100dvh, var(--frame-device-height, 812px));
8
- --page-frame-header-padding-top: var(--frame-safe-area-top);
9
- --page-frame-footer-safe-area: var(--frame-safe-area-bottom);
10
- --page-frame-max-width: var(
7
+ --uds-page-frame-height: min(
8
+ 100svh,
9
+ 100dvh,
10
+ var(--frame-device-height, 812px)
11
+ );
12
+ --uds-page-frame-header-padding-top: var(--frame-safe-area-top);
13
+ --uds-page-frame-footer-safe-area: var(--frame-safe-area-bottom);
14
+ --uds-page-frame-max-width: var(
11
15
  --frame-device-width,
12
16
  480px
13
17
  );
@@ -49,9 +53,9 @@
49
53
  .page-frame-container {
50
54
  display: grid;
51
55
  grid-template-rows: auto 1fr auto;
52
- width: min(100%, var(--page-frame-max-width));
56
+ width: min(100%, var(--uds-page-frame-max-width));
53
57
  margin: 0 auto;
54
- height: var(--page-frame-height);
58
+ height: var(--uds-page-frame-height);
55
59
  background-color: var(--color-common-100, #ffffff);
56
60
  color: inherit;
57
61
  }
@@ -63,7 +67,7 @@
63
67
  }
64
68
 
65
69
  .page-frame-header {
66
- padding-top: var(--page-frame-header-padding-top);
70
+ padding-top: var(--uds-page-frame-header-padding-top);
67
71
  }
68
72
 
69
73
  .page-frame-body {
@@ -72,13 +76,13 @@
72
76
  }
73
77
 
74
78
  .page-frame-footer {
75
- padding-bottom: var(--page-frame-footer-safe-area);
79
+ padding-bottom: var(--uds-page-frame-footer-safe-area);
76
80
  }
77
81
 
78
82
  .page-frame-mobile {
79
83
  width: 100%;
80
84
  height: 100%;
81
- min-height: var(--page-frame-height, var(--frame-device-height, 812px));
85
+ min-height: var(--uds-page-frame-height, var(--frame-device-height, 812px));
82
86
  max-height: 100dvh;
83
87
  background-color: var(--color-common-100, #ffffff);
84
88
  }
@@ -536,7 +540,7 @@
536
540
  min-width: 230px;
537
541
  padding: 0 var(--spacing-padding-6, 16px);
538
542
  border-radius: var(--theme-radius-medium-3, 8px);
539
- background: var(--color-background-alternative-cool_gray, var(--color-cool-gray-95, #f2f2f3));
543
+ background: var(--color-background-alternative-cool-gray, var(--color-cool-gray-95, #f2f2f3));
540
544
  }
541
545
 
542
546
  .auth-find-account-id-summary-row {
@@ -779,3 +783,128 @@
779
783
  line-height: 1.5em;
780
784
  letter-spacing: 0px;
781
785
  }
786
+
787
+ .weather-base-container, .weather-next-days-container, .weather-today-container {
788
+ width: fit-content;
789
+ display: flex;
790
+ align-items: center;
791
+ }
792
+ .weather-base-container span, .weather-next-days-container span, .weather-today-container span {
793
+ font-size: 1.4rem;
794
+ }
795
+
796
+ .weather-base-icon {
797
+ width: 2.5rem;
798
+ height: 2.4rem;
799
+ position: relative;
800
+ }
801
+
802
+ .weather-base-text-info, .weather-next-days-text {
803
+ display: flex;
804
+ align-items: center;
805
+ }
806
+ .weather-base-text-info dt, .weather-next-days-text dt {
807
+ margin-right: 0.8rem;
808
+ font-size: 0;
809
+ }
810
+ .weather-base-text-info dt span, .weather-next-days-text dt span {
811
+ color: var(--color-cool-gray-35);
812
+ }
813
+ .weather-base-text-info dd, .weather-next-days-text dd {
814
+ font-size: 0;
815
+ display: flex;
816
+ align-items: flex-start;
817
+ }
818
+ .weather-base-text-info dd span, .weather-next-days-text dd span {
819
+ color: var(--color-cool-gray-10);
820
+ font-weight: 600;
821
+ }
822
+
823
+ .weather-base-divider {
824
+ width: 1px;
825
+ height: 1.3rem;
826
+ margin: 0 0.8rem;
827
+ background: var(--color-cool-gray-85);
828
+ }
829
+
830
+ .weather-address {
831
+ display: flex;
832
+ align-items: center;
833
+ width: fit-content;
834
+ }
835
+ .weather-address-icon {
836
+ width: 1.6rem;
837
+ height: 1.6rem;
838
+ margin-right: 0.4rem;
839
+ position: relative;
840
+ }
841
+ .weather-address-text {
842
+ width: fit-content;
843
+ margin-right: 1rem;
844
+ display: flex;
845
+ align-items: center;
846
+ }
847
+ .weather-address-text span {
848
+ font-size: 1.6rem;
849
+ color: var(--color-cool-gray-10);
850
+ font-weight: 600;
851
+ white-space: nowrap;
852
+ transform: translateY(0.1rem);
853
+ }
854
+
855
+ .weather-today-container span {
856
+ white-space: nowrap;
857
+ }
858
+ .weather-today-container .empty-text {
859
+ font-size: 1.4rem;
860
+ padding-right: 4rem;
861
+ }
862
+
863
+ .weather-today-temperature {
864
+ margin-left: 1rem;
865
+ display: flex;
866
+ align-items: flex-start;
867
+ }
868
+ .weather-today-temperature span {
869
+ font-size: 2rem;
870
+ font-weight: 700;
871
+ color: var(--color-blue-55);
872
+ }
873
+ .weather-today-temperature .unit {
874
+ font-size: 1.2rem;
875
+ }
876
+
877
+ .weather-today-humidity {
878
+ margin-left: 1.4rem;
879
+ }
880
+
881
+ .weather-alert {
882
+ width: fit-content;
883
+ height: 2.6rem;
884
+ padding: 0 1rem;
885
+ margin-left: 1rem;
886
+ border-radius: 0.8rem;
887
+ background-color: var(--color-cool-gray-95, #f4f6f8);
888
+ display: flex;
889
+ align-items: center;
890
+ }
891
+ .weather-alert-text {
892
+ height: fit-content;
893
+ font-size: 0;
894
+ }
895
+ .weather-alert-text span {
896
+ font-size: 1.2rem;
897
+ color: var(--color-blue-55, #007cdb);
898
+ font-weight: 600;
899
+ line-height: 1.6;
900
+ }
901
+
902
+ .weather-next-days-text dt {
903
+ margin-right: 0.8rem;
904
+ }
905
+ .weather-next-days-text dd:nth-of-type(n + 2) {
906
+ margin-left: 0.8rem;
907
+ }
908
+ .weather-next-days-text dd:nth-of-type(n + 2) .unit {
909
+ font-size: 1rem;
910
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-templates",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "UNIAI Design System; UI Templates Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -12,7 +12,7 @@
12
12
  "publishConfig": {
13
13
  "access": "public"
14
14
  },
15
- "packageManager": "pnpm@10.27.0",
15
+ "packageManager": "pnpm@10.28.0",
16
16
  "engines": {
17
17
  "node": ">=24",
18
18
  "pnpm": ">=10"
@@ -48,13 +48,16 @@
48
48
  "design-templates:dev": "pnpm run dev"
49
49
  },
50
50
  "peerDependencies": {
51
- "@uniai-fe/uds-foundation": "^0.0.1",
52
- "@uniai-fe/uds-primitives": "^0.0.2",
53
- "react": ">= 19",
54
- "react-dom": ">= 19",
55
- "react-hook-form": ">= 7",
51
+ "@uniai-fe/uds-foundation": "^0.1.0",
52
+ "@uniai-fe/uds-primitives": "^0.1.0",
53
+ "@uniai-fe/util-functions": "^0.2.0",
54
+ "@uniai-fe/util-next": "^0.2.0",
55
+ "@tanstack/react-query": "^5",
56
+ "jotai": "^2",
56
57
  "next": "^15",
57
- "jotai": ">= 2"
58
+ "react": "^19",
59
+ "react-dom": "^19",
60
+ "react-hook-form": "^7"
58
61
  },
59
62
  "dependencies": {
60
63
  "clsx": "^2.1.1",
@@ -63,18 +66,22 @@
63
66
  "devDependencies": {
64
67
  "@svgr/webpack": "^8.1.0",
65
68
  "@types/node": "^24.10.2",
66
- "@types/react": "^19.2.7",
69
+ "@types/react": "^19.2.8",
67
70
  "@types/react-dom": "^19.2.3",
68
71
  "@uniai-fe/eslint-config": "workspace:*",
69
72
  "@uniai-fe/next-devkit": "workspace:*",
73
+ "@uniai-fe/react-hooks": "workspace:*",
70
74
  "@uniai-fe/tsconfig": "workspace:*",
71
75
  "@uniai-fe/uds-foundation": "workspace:*",
72
76
  "@uniai-fe/uds-primitives": "workspace:*",
77
+ "@uniai-fe/util-functions": "workspace:*",
78
+ "@uniai-fe/util-next": "workspace:*",
79
+ "@tanstack/react-query": "^5.90.16",
73
80
  "eslint": "^9.39.2",
81
+ "jotai": "^2.16.1",
74
82
  "next": "^15.5.9",
75
83
  "prettier": "^3.7.4",
76
- "react-hook-form": "^7.70.0",
77
- "jotai": "^2.16.1",
84
+ "react-hook-form": "^7.71.0",
78
85
  "sass": "^1.97.2",
79
86
  "typescript": "~5.9.3"
80
87
  }
@@ -17,6 +17,9 @@ export default function AuthCompleteTemplate({
17
17
  }: AuthCompleteTemplateProps) {
18
18
  const confirmButtonProps = {
19
19
  type: "button" as const,
20
+ fill: "solid" as const,
21
+ size: "medium" as const,
22
+ priority: "primary" as const,
20
23
  block: true,
21
24
  ...cta?.buttonProps,
22
25
  };
@@ -3,7 +3,10 @@ import type { ButtonProps } from "@uniai-fe/uds-primitives";
3
3
 
4
4
  export type AuthCompleteCTA = {
5
5
  label: ReactNode;
6
- buttonProps?: Omit<ButtonProps, "children">;
6
+ /**
7
+ * Button 기본 옵션은 템플릿이 채우므로 부분 지정만 허용한다.
8
+ */
9
+ buttonProps?: Partial<Omit<ButtonProps, "children">>;
7
10
  };
8
11
 
9
12
  export type AuthCompleteTemplateProps = {
@@ -59,7 +59,8 @@ export function FindAccountCodeStep({
59
59
 
60
60
  const submitButtonProps = {
61
61
  type: "submit" as const,
62
- scale: "solid-xlarge" as const,
62
+ fill: "solid" as const,
63
+ size: "xlarge" as const,
63
64
  priority: "primary" as const,
64
65
  block: true,
65
66
  ...cta?.buttonProps,
@@ -60,7 +60,8 @@ export function FindAccountInfoStep({
60
60
 
61
61
  const buttonProps = {
62
62
  type: "submit" as const,
63
- scale: "solid-xlarge" as const,
63
+ fill: "solid" as const,
64
+ size: "xlarge" as const,
64
65
  priority: "primary" as const,
65
66
  block: true,
66
67
  ...cta?.buttonProps,
@@ -48,7 +48,7 @@
48
48
  padding: 0 var(--spacing-padding-6, 16px);
49
49
  border-radius: var(--theme-radius-medium-3, 8px);
50
50
  background: var(
51
- --color-background-alternative-cool_gray,
51
+ --color-background-alternative-cool-gray,
52
52
  var(--color-cool-gray-95, #f2f2f3)
53
53
  );
54
54
  }
@@ -80,7 +80,8 @@ export default function FindPasswordStepReset({
80
80
 
81
81
  const buttonProps = {
82
82
  type: "submit" as const,
83
- scale: "solid-xlarge" as const,
83
+ fill: "solid" as const,
84
+ size: "xlarge" as const,
84
85
  priority: "primary" as const,
85
86
  block: true,
86
87
  ...cta?.buttonProps,
@@ -104,7 +104,8 @@ export default function AuthLoginFormField<
104
104
  />
105
105
  <Button.Default
106
106
  type="submit"
107
- scale="solid-xlarge"
107
+ fill="solid"
108
+ size="xlarge"
108
109
  priority="primary"
109
110
  block
110
111
  disabled={disabled}
@@ -101,7 +101,8 @@ export function AuthSignupAccountForm({
101
101
  </div>
102
102
  <Button.Default
103
103
  type="submit"
104
- scale="solid-xlarge"
104
+ fill="solid"
105
+ size="xlarge"
105
106
  priority="primary"
106
107
  block
107
108
  disabled={disabled}
@@ -24,7 +24,8 @@ export function AuthSignupComplete({
24
24
  <div className="auth-signup-complete-actions">
25
25
  <Button.Default
26
26
  type="button"
27
- scale="solid-xlarge"
27
+ fill="solid"
28
+ size="xlarge"
28
29
  priority="secondary"
29
30
  block
30
31
  disabled={secondaryAction.disabled}
@@ -95,7 +95,8 @@ export function AuthSignupUserInfoForm({
95
95
  </div>
96
96
  <Button.Default
97
97
  type="submit"
98
- scale="solid-xlarge"
98
+ fill="solid"
99
+ size="xlarge"
99
100
  priority="primary"
100
101
  block
101
102
  disabled={disabled}
@@ -338,7 +338,8 @@ export function AuthSignupVerificationForm({
338
338
  ) : null}
339
339
  <Button.Default
340
340
  type="submit"
341
- scale="solid-xlarge"
341
+ fill="solid"
342
+ size="xlarge"
342
343
  priority="primary"
343
344
  block
344
345
  disabled={ctaDisabled}
@@ -367,7 +368,8 @@ export function AuthSignupVerificationForm({
367
368
  <DrawerFooter>
368
369
  <Button.Default
369
370
  type="button"
370
- scale="solid-medium"
371
+ fill="solid"
372
+ size="medium"
371
373
  priority="primary"
372
374
  block
373
375
  onClick={handleCloseDrawer}
package/src/index.scss CHANGED
@@ -14,3 +14,4 @@
14
14
  @use "./auth/find-id/index.scss" as authFindId;
15
15
  @use "./auth/find-password/index.scss" as authFindPassword;
16
16
  @use "./auth/signup/styles/signup.scss" as authSignup;
17
+ @use "./weather/styles/weather.scss" as weatherStyles;
package/src/index.tsx CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./page-frame";
2
2
  export * from "./auth";
3
3
  export * from "./modal";
4
+ export * from "./weather";
@@ -117,11 +117,14 @@ export function ModalFooterButtons({
117
117
  );
118
118
  }
119
119
 
120
+ const fill = defaultOptions.fill ?? "solid";
121
+ const size = defaultOptions.size ?? "large";
120
122
  return (
121
123
  <Button.Default
122
124
  key={buttonKey}
123
125
  {...commonProps}
124
- scale={defaultOptions.scale ?? "solid-large"}
126
+ fill={fill}
127
+ size={size}
125
128
  priority={defaultOptions.priority ?? "primary"}
126
129
  >
127
130
  {defaultOptions.label}
@@ -73,7 +73,8 @@ export function createDialogModal({
73
73
  // cancel 버튼은 secondary solid 규격으로 일관된 스킨을 유지한다.
74
74
  defaultOptions: {
75
75
  label: cancel.label ?? DEFAULT_DIALOG_CANCEL,
76
- scale: "solid-large",
76
+ fill: "solid",
77
+ size: "large",
77
78
  priority: "secondary",
78
79
  disabled: cancel.disabled,
79
80
  onClick: cancel.onClick,
@@ -88,7 +89,8 @@ export function createDialogModal({
88
89
  // confirm 버튼은 primary solid 규격으로 고정한다.
89
90
  defaultOptions: {
90
91
  label: primary.label ?? DEFAULT_DIALOG_CONFIRM,
91
- scale: "solid-large",
92
+ fill: "solid",
93
+ size: "large",
92
94
  priority: "primary",
93
95
  disabled: primary.disabled,
94
96
  onClick: primary.onClick,
@@ -1,7 +1,8 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type {
3
+ ButtonFill,
3
4
  ButtonPriority,
4
- ButtonScale,
5
+ ButtonSize,
5
6
  TextButtonSize,
6
7
  } from "@uniai-fe/uds-primitives";
7
8
 
@@ -13,7 +14,8 @@ export type ModalFooterButtonDefaultOptions = {
13
14
  disabled?: boolean;
14
15
  block?: boolean;
15
16
  priority?: ButtonPriority;
16
- scale?: ButtonScale;
17
+ fill?: ButtonFill;
18
+ size?: ButtonSize;
17
19
  textSize?: TextButtonSize;
18
20
  appearance?: ModalFooterButtonAppearance;
19
21
  className?: string;
@@ -3,10 +3,14 @@
3
3
  --frame-device-width: 480px;
4
4
  --frame-safe-area-top: env(safe-area-inset-top, 0px);
5
5
  --frame-safe-area-bottom: env(safe-area-inset-bottom, 0px);
6
- --page-frame-height: min(100svh, 100dvh, var(--frame-device-height, 812px));
7
- --page-frame-header-padding-top: var(--frame-safe-area-top);
8
- --page-frame-footer-safe-area: var(--frame-safe-area-bottom);
9
- --page-frame-max-width: var(
6
+ --uds-page-frame-height: min(
7
+ 100svh,
8
+ 100dvh,
9
+ var(--frame-device-height, 812px)
10
+ );
11
+ --uds-page-frame-header-padding-top: var(--frame-safe-area-top);
12
+ --uds-page-frame-footer-safe-area: var(--frame-safe-area-bottom);
13
+ --uds-page-frame-max-width: var(
10
14
  --frame-device-width,
11
15
  480px
12
16
  ); // PC에선 지정 폭으로 중앙 정렬하고, 모바일에선 100%를 유지
@@ -15,9 +19,11 @@
15
19
  .page-frame-container {
16
20
  display: grid;
17
21
  grid-template-rows: auto 1fr auto;
18
- width: min(100%, var(--page-frame-max-width));
22
+ width: min(100%, var(--uds-page-frame-max-width));
19
23
  margin: 0 auto; // 폭이 남는 PC 환경에서만 중앙 정렬
20
- height: var(--page-frame-height); // viewport 변동 시에도 정확한 높이를 강제
24
+ height: var(
25
+ --uds-page-frame-height
26
+ ); // viewport 변동 시에도 정확한 높이를 강제
21
27
  background-color: var(--color-common-100, #ffffff);
22
28
  color: inherit;
23
29
  }
@@ -29,7 +35,7 @@
29
35
  }
30
36
 
31
37
  .page-frame-header {
32
- padding-top: var(--page-frame-header-padding-top);
38
+ padding-top: var(--uds-page-frame-header-padding-top);
33
39
  }
34
40
 
35
41
  .page-frame-body {
@@ -38,5 +44,5 @@
38
44
  }
39
45
 
40
46
  .page-frame-footer {
41
- padding-bottom: var(--page-frame-footer-safe-area);
47
+ padding-bottom: var(--uds-page-frame-footer-safe-area);
42
48
  }
@@ -0,0 +1,22 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import type { ReactNode } from "react";
5
+ import PageHeaderSection from "./Section";
6
+
7
+ export default function PageHeaderContainer({
8
+ className,
9
+ left,
10
+ right,
11
+ }: {
12
+ className?: string;
13
+ left: ReactNode;
14
+ right: ReactNode;
15
+ }) {
16
+ return (
17
+ <header className={clsx("page-frame-header", className)}>
18
+ <PageHeaderSection direction="left">{left}</PageHeaderSection>
19
+ <PageHeaderSection direction="right">{right}</PageHeaderSection>
20
+ </header>
21
+ );
22
+ }
@@ -0,0 +1,20 @@
1
+ import clsx from "clsx";
2
+ import type { ComponentBaseProps } from "../../../types";
3
+
4
+ export default function PageHeaderSection({
5
+ className,
6
+ direction,
7
+ children,
8
+ }: ComponentBaseProps & { direction: "left" | "right" }) {
9
+ return (
10
+ <div
11
+ className={clsx(
12
+ "page-frame-header-section",
13
+ `page-frame-header-${direction}`,
14
+ className,
15
+ )}
16
+ >
17
+ {children}
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1,19 @@
1
+ import PageHeaderContainer from "./Container";
2
+ import PageHeaderUtilityContainer from "./util/Container";
3
+ import PageHeaderUtilityItem from "./util/Item";
4
+ import PageHeaderUtilityButton from "./util/Button";
5
+ import PageHeaderSettingButton from "./util/setting/Button";
6
+ import PageHeaderLogoutButton from "./util/Logout";
7
+
8
+ const Header = {
9
+ Container: PageHeaderContainer,
10
+ Utility: {
11
+ Container: PageHeaderUtilityContainer,
12
+ Item: PageHeaderUtilityItem,
13
+ Button: PageHeaderUtilityButton,
14
+ },
15
+ Setting: PageHeaderSettingButton,
16
+ Logout: PageHeaderLogoutButton,
17
+ };
18
+
19
+ export default Header;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import { Button, type TextButtonProps } from "@uniai-fe/uds-primitives";
5
+
6
+ export default function PageHeaderUtilityButton({
7
+ as,
8
+ className,
9
+ children,
10
+ ...props
11
+ }: Omit<TextButtonProps, "size" | "priority">) {
12
+ return (
13
+ <Button.Text
14
+ as={as ?? "button"}
15
+ className={clsx("page-header-util-button", className)}
16
+ {...props}
17
+ priority="tertiary"
18
+ size="medium"
19
+ >
20
+ {children}
21
+ </Button.Text>
22
+ );
23
+ }
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import type { ReactNode } from "react";
5
+
6
+ export default function PageHeaderUtilityContainer({
7
+ className,
8
+ children,
9
+ }: {
10
+ className?: string;
11
+ children: ReactNode;
12
+ }) {
13
+ return (
14
+ <ul className={clsx("page-header-util-container", className)}>
15
+ {children}
16
+ </ul>
17
+ );
18
+ }
@@ -0,0 +1,13 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import type { ComponentBaseProps } from "../../../../types";
5
+
6
+ export default function PageHeaderUtilityItem({
7
+ className,
8
+ children,
9
+ }: ComponentBaseProps) {
10
+ return (
11
+ <li className={clsx("page-header-util-item", className)}>{children}</li>
12
+ );
13
+ }
@@ -0,0 +1,26 @@
1
+ "use client";
2
+
3
+ import Image from "next/image";
4
+ import Link from "next/link";
5
+ import assetsUrl from "../../../../../../asset-url";
6
+ import PageHeaderUtilityItem from "./Item";
7
+ import PageHeaderUtilityButton from "./Button";
8
+
9
+ export default function PageHeaderLogoutButton({
10
+ path = "/logout",
11
+ }: {
12
+ path?: string;
13
+ }) {
14
+ return (
15
+ <PageHeaderUtilityItem>
16
+ <PageHeaderUtilityButton href={path} as={Link}>
17
+ <Image
18
+ src={`${assetsUrl}/img/page-header/logout-v2.svg`}
19
+ alt="로그아웃"
20
+ width={24}
21
+ height={24}
22
+ />
23
+ </PageHeaderUtilityButton>
24
+ </PageHeaderUtilityItem>
25
+ );
26
+ }