Merge branch 'integration/layout-design-system' into feature/layout-design-system
@c0cc4242435e14935f88d3e7194a2e7a5806504f
--- app/favicon.ico
+++ app/favicon.ico
| Binary file is not shown |
--- app/globals.css
+++ app/globals.css
... | ... | @@ -1,31 +1,266 @@ |
| 1 | 1 |
@import "tailwindcss"; |
| 2 | 2 |
|
| 3 |
+/* 색상 토큰 출처: edupay-app lib/ui/core/tokens/app_colors.dart |
|
| 4 |
+ (Figma: FOX PAY 1차 제작시안, node 91:10205). 다크 값은 edupay-app에 대응 |
|
| 5 |
+ 다크 시안이 없어 "임의 도출 — 정식 다크 시안 확정 시 교체". */ |
|
| 3 | 6 |
:root {
|
| 7 |
+ /* Brand */ |
|
| 8 |
+ --primary: #502ef5; |
|
| 9 |
+ --primary-container: #6d5ce7; |
|
| 10 |
+ --on-primary: #ffffff; |
|
| 11 |
+ --on-primary-container: #e2e0fb; |
|
| 12 |
+ --secondary: #877aec; |
|
| 13 |
+ |
|
| 14 |
+ /* Surface / background (원 토큰: surface / gray0) */ |
|
| 4 | 15 |
--background: #ffffff; |
| 5 |
- --foreground: #171717; |
|
| 6 |
- /* 임시 값, 디자인 확정 전 — 에러/위험 상태 표시용 (initial-security-setup, 로그인 실패 메시지) */ |
|
| 7 |
- --danger: #dc2626; |
|
| 16 |
+ --surface: #ffffff; |
|
| 17 |
+ --surface-muted: #f5f7fa; |
|
| 18 |
+ |
|
| 19 |
+ /* Foreground / text (원 토큰: onSurface 계열) */ |
|
| 20 |
+ --foreground: #2c3038; |
|
| 21 |
+ --foreground-muted: #565d6b; |
|
| 22 |
+ --foreground-subtle: #6b7486; |
|
| 23 |
+ |
|
| 24 |
+ /* Border (원 토큰: outline 계열) */ |
|
| 25 |
+ --border: #dae0ed; |
|
| 26 |
+ --border-subtle: #e1e6f1; |
|
| 27 |
+ |
|
| 28 |
+ /* Status */ |
|
| 29 |
+ --danger: #ec2e2f; |
|
| 30 |
+ --success: #21b770; |
|
| 31 |
+ --warning: #ffb020; |
|
| 32 |
+ |
|
| 33 |
+ /* Overlay */ |
|
| 34 |
+ --scrim: rgb(0 0 0 / 0.6); |
|
| 8 | 35 |
} |
| 9 | 36 |
|
| 10 | 37 |
@theme inline {
|
| 38 |
+ --color-primary: var(--primary); |
|
| 39 |
+ --color-primary-container: var(--primary-container); |
|
| 40 |
+ --color-on-primary: var(--on-primary); |
|
| 41 |
+ --color-on-primary-container: var(--on-primary-container); |
|
| 42 |
+ --color-secondary: var(--secondary); |
|
| 43 |
+ |
|
| 11 | 44 |
--color-background: var(--background); |
| 45 |
+ --color-surface: var(--surface); |
|
| 46 |
+ --color-surface-muted: var(--surface-muted); |
|
| 47 |
+ |
|
| 12 | 48 |
--color-foreground: var(--foreground); |
| 49 |
+ --color-foreground-muted: var(--foreground-muted); |
|
| 50 |
+ --color-foreground-subtle: var(--foreground-subtle); |
|
| 51 |
+ |
|
| 52 |
+ --color-border: var(--border); |
|
| 53 |
+ --color-border-subtle: var(--border-subtle); |
|
| 54 |
+ |
|
| 13 | 55 |
--color-danger: var(--danger); |
| 14 |
- --font-sans: var(--font-geist-sans); |
|
| 56 |
+ --color-success: var(--success); |
|
| 57 |
+ --color-warning: var(--warning); |
|
| 58 |
+ |
|
| 59 |
+ --color-scrim: var(--scrim); |
|
| 60 |
+ |
|
| 61 |
+ /* WantedSans 연결 전에도 안전한 폴백(feature 레인이 app/layout.tsx에서 |
|
| 62 |
+ next/font/local로 --font-wanted-sans를 정의). */ |
|
| 63 |
+ --font-sans: var(--font-wanted-sans, system-ui), system-ui, |
|
| 64 |
+ "Apple SD Gothic Neo", sans-serif; |
|
| 15 | 65 |
--font-mono: var(--font-geist-mono); |
| 66 |
+ |
|
| 67 |
+ /* 타이포 토큰 출처: edupay-app lib/ui/core/tokens/app_typography.dart |
|
| 68 |
+ (Figma: FOX PAY 1차 제작시안, node 112:11439). voucher/numeric 등 앱 전용 |
|
| 69 |
+ 스타일은 어드민 무관으로 미이식. */ |
|
| 70 |
+ --text-display-lg: 3rem; |
|
| 71 |
+ --text-display-lg--line-height: 1.5; |
|
| 72 |
+ --text-display-lg--font-weight: 700; |
|
| 73 |
+ |
|
| 74 |
+ --text-display-md: 2.25rem; |
|
| 75 |
+ --text-display-md--line-height: 1.5; |
|
| 76 |
+ --text-display-md--letter-spacing: -0.72px; |
|
| 77 |
+ --text-display-md--font-weight: 700; |
|
| 78 |
+ |
|
| 79 |
+ --text-display-sm: 2rem; |
|
| 80 |
+ --text-display-sm--line-height: 1.5; |
|
| 81 |
+ --text-display-sm--letter-spacing: -0.64px; |
|
| 82 |
+ --text-display-sm--font-weight: 700; |
|
| 83 |
+ |
|
| 84 |
+ --text-headline-lg: 1.5rem; |
|
| 85 |
+ --text-headline-lg--line-height: 1.5; |
|
| 86 |
+ --text-headline-lg--letter-spacing: -0.72px; |
|
| 87 |
+ --text-headline-lg--font-weight: 700; |
|
| 88 |
+ |
|
| 89 |
+ --text-headline-md: 1.25rem; |
|
| 90 |
+ --text-headline-md--line-height: 1.5; |
|
| 91 |
+ --text-headline-md--letter-spacing: -0.6px; |
|
| 92 |
+ --text-headline-md--font-weight: 700; |
|
| 93 |
+ |
|
| 94 |
+ --text-headline-sm: 1.125rem; |
|
| 95 |
+ --text-headline-sm--line-height: 1.3; |
|
| 96 |
+ --text-headline-sm--letter-spacing: -0.54px; |
|
| 97 |
+ --text-headline-sm--font-weight: 700; |
|
| 98 |
+ |
|
| 99 |
+ --text-title-lg: 1.125rem; |
|
| 100 |
+ --text-title-lg--line-height: 1.5; |
|
| 101 |
+ --text-title-lg--font-weight: 700; |
|
| 102 |
+ |
|
| 103 |
+ --text-title-md: 1rem; |
|
| 104 |
+ --text-title-md--line-height: 1.5; |
|
| 105 |
+ --text-title-md--font-weight: 700; |
|
| 106 |
+ |
|
| 107 |
+ --text-title-sm: 0.875rem; |
|
| 108 |
+ --text-title-sm--line-height: 1.5; |
|
| 109 |
+ --text-title-sm--letter-spacing: -0.42px; |
|
| 110 |
+ --text-title-sm--font-weight: 700; |
|
| 111 |
+ |
|
| 112 |
+ --text-body-lg: 1rem; |
|
| 113 |
+ --text-body-lg--line-height: 1.5; |
|
| 114 |
+ --text-body-lg--letter-spacing: -0.48px; |
|
| 115 |
+ --text-body-lg--font-weight: 400; |
|
| 116 |
+ |
|
| 117 |
+ --text-body-md: 0.875rem; |
|
| 118 |
+ --text-body-md--line-height: 1.5; |
|
| 119 |
+ --text-body-md--font-weight: 400; |
|
| 120 |
+ |
|
| 121 |
+ --text-body-sm: 0.75rem; |
|
| 122 |
+ --text-body-sm--line-height: 1.5; |
|
| 123 |
+ --text-body-sm--font-weight: 400; |
|
| 124 |
+ |
|
| 125 |
+ --text-label-lg: 1rem; |
|
| 126 |
+ --text-label-lg--line-height: 1.4; |
|
| 127 |
+ --text-label-lg--letter-spacing: -0.48px; |
|
| 128 |
+ --text-label-lg--font-weight: 700; |
|
| 129 |
+ |
|
| 130 |
+ --text-label-md: 0.875rem; |
|
| 131 |
+ --text-label-md--letter-spacing: -0.28px; |
|
| 132 |
+ --text-label-md--font-weight: 400; |
|
| 133 |
+ |
|
| 134 |
+ --text-label-sm: 0.75rem; |
|
| 135 |
+ --text-label-sm--font-weight: 400; |
|
| 136 |
+ |
|
| 137 |
+ /* radius 토큰 출처: edupay-app lib/ui/core/tokens/app_radius.dart. |
|
| 138 |
+ Tailwind 기본 스케일과 다른 값으로 의도적 재정의(앱 일체화, §8-4 승인). |
|
| 139 |
+ pill(999)은 기본 rounded-full로 충족, card(50)은 앱 전용 미이식. */ |
|
| 140 |
+ --radius-xs: 0.25rem; |
|
| 141 |
+ --radius-sm: 0.5rem; |
|
| 142 |
+ --radius-md: 0.75rem; |
|
| 143 |
+ --radius-lg: 1rem; |
|
| 144 |
+ --radius-xl: 1.25rem; |
|
| 145 |
+ --radius-2xl: 1.5rem; |
|
| 146 |
+ |
|
| 147 |
+ /* 그림자 토큰 출처: edupay-app lib/ui/core/tokens/app_shadows.dart — |
|
| 148 |
+ 어드민 유효분만 이식(voucher inner·stacked 등 미이식). */ |
|
| 149 |
+ --shadow-card: 0 5.16px 5.16px rgb(93 100 119 / 0.12); |
|
| 150 |
+ --shadow-list-card: 0 8px 16px rgb(93 100 119 / 0.12); |
|
| 151 |
+ --shadow-dialog: 0 8px 16px rgb(0 0 0 / 0.1); |
|
| 152 |
+ --shadow-popup: 2.16px 6.32px 6px rgb(177 178 180 / 0.24); |
|
| 153 |
+ --shadow-toast: 0 10px 12px rgb(0 0 0 / 0.2); |
|
| 154 |
+ --shadow-subtle: 0 2px 4px rgb(0 0 0 / 0.08); |
|
| 155 |
+ |
|
| 156 |
+ /* 애니메이션 토큰(신규 네임스페이스 — §4.2 승인). 오버레이·다이얼로그·토스트· |
|
| 157 |
+ 스플래시 전환에 사용. --ease-*는 Tailwind 기본값 사용, duration은 값에 |
|
| 158 |
+ 내장(트랜지션 duration 표준은 bare 유틸 duration-200 사용). */ |
|
| 159 |
+ --animate-fade-in: fade-in 200ms var(--ease-out); |
|
| 160 |
+ --animate-fade-out: fade-out 200ms var(--ease-in); |
|
| 161 |
+ --animate-dialog-in: dialog-in 200ms var(--ease-out); |
|
| 162 |
+ --animate-toast-in: toast-in 300ms var(--ease-out); |
|
| 16 | 163 |
} |
| 164 |
+ |
|
| 165 |
+@keyframes fade-in {
|
|
| 166 |
+ from {
|
|
| 167 |
+ opacity: 0; |
|
| 168 |
+ } |
|
| 169 |
+ to {
|
|
| 170 |
+ opacity: 1; |
|
| 171 |
+ } |
|
| 172 |
+} |
|
| 173 |
+ |
|
| 174 |
+/* 스플래시 등 풀스크린 오버레이 퇴장용 — fade-in의 역방향 키프레임. */ |
|
| 175 |
+@keyframes fade-out {
|
|
| 176 |
+ from {
|
|
| 177 |
+ opacity: 1; |
|
| 178 |
+ } |
|
| 179 |
+ to {
|
|
| 180 |
+ opacity: 0; |
|
| 181 |
+ } |
|
| 182 |
+} |
|
| 183 |
+ |
|
| 184 |
+@keyframes dialog-in {
|
|
| 185 |
+ from {
|
|
| 186 |
+ opacity: 0; |
|
| 187 |
+ transform: scale(0.95); |
|
| 188 |
+ } |
|
| 189 |
+ to {
|
|
| 190 |
+ opacity: 1; |
|
| 191 |
+ transform: scale(1); |
|
| 192 |
+ } |
|
| 193 |
+} |
|
| 194 |
+ |
|
| 195 |
+@keyframes toast-in {
|
|
| 196 |
+ from {
|
|
| 197 |
+ opacity: 0; |
|
| 198 |
+ transform: translateY(0.5rem); |
|
| 199 |
+ } |
|
| 200 |
+ to {
|
|
| 201 |
+ opacity: 1; |
|
| 202 |
+ transform: translateY(0); |
|
| 203 |
+ } |
|
| 204 |
+} |
|
| 205 |
+ |
|
| 206 |
+/* 레이아웃 전환(View Transitions API, feature 레인 A안) — feature가 전환 루트 |
|
| 207 |
+ 요소에 .layout-transition 클래스를 부여하면 view-transition-class로 매핑되어 |
|
| 208 |
+ 아래 ::view-transition-old/new 규칙이 부드러운 크로스페이드를 적용한다. |
|
| 209 |
+ (CSS View Transitions Level 2, view-transition-class 프로퍼티 사용) */ |
|
| 210 |
+.layout-transition {
|
|
| 211 |
+ view-transition-class: layout-transition; |
|
| 212 |
+} |
|
| 213 |
+ |
|
| 214 |
+::view-transition-old(.layout-transition) {
|
|
| 215 |
+ animation: var(--animate-fade-out); |
|
| 216 |
+} |
|
| 217 |
+ |
|
| 218 |
+::view-transition-new(.layout-transition) {
|
|
| 219 |
+ animation: var(--animate-fade-in); |
|
| 220 |
+} |
|
| 221 |
+ |
|
| 222 |
+@media (prefers-reduced-motion: reduce) {
|
|
| 223 |
+ ::view-transition-group(.layout-transition), |
|
| 224 |
+ ::view-transition-old(.layout-transition), |
|
| 225 |
+ ::view-transition-new(.layout-transition) {
|
|
| 226 |
+ animation: none; |
|
| 227 |
+ } |
|
| 228 |
+} |
|
| 229 |
+ |
|
| 230 |
+/* 간격 토큰 매핑 (변경 없음) — edupay-app lib/ui/core/tokens/app_spacing.dart의 |
|
| 231 |
+ 4/8 grid(4·8·12·16·20·24·32·40)는 Tailwind 기본 --spacing: 0.25rem 스케일과 |
|
| 232 |
+ 정확히 일치한다: xs→p-1, sm→p-2, md→p-3, lg→p-4, xl→p-5, xxl→p-6, |
|
| 233 |
+ xxxl→p-8, xxxxl→p-10. */ |
|
| 17 | 234 |
|
| 18 | 235 |
@media (prefers-color-scheme: dark) {
|
| 19 | 236 |
:root {
|
| 20 |
- --background: #0a0a0a; |
|
| 21 |
- --foreground: #ededed; |
|
| 22 |
- /* 임시 값, 디자인 확정 전 */ |
|
| 23 |
- --danger: #f87171; |
|
| 237 |
+ /* 임의 도출 — 정식 다크 시안 확정 시 교체 */ |
|
| 238 |
+ --primary: #877aec; |
|
| 239 |
+ --primary-container: #502ef5; |
|
| 240 |
+ --on-primary: #ffffff; |
|
| 241 |
+ --on-primary-container: #e2e0fb; |
|
| 242 |
+ --secondary: #9b92ff; |
|
| 243 |
+ |
|
| 244 |
+ --background: #14161b; |
|
| 245 |
+ --surface: #1e2128; |
|
| 246 |
+ --surface-muted: #2c3038; |
|
| 247 |
+ |
|
| 248 |
+ --foreground: #f5f7fa; |
|
| 249 |
+ --foreground-muted: #bbc4d8; |
|
| 250 |
+ --foreground-subtle: #8790a3; |
|
| 251 |
+ |
|
| 252 |
+ --border: #424752; |
|
| 253 |
+ --border-subtle: #343943; |
|
| 254 |
+ |
|
| 255 |
+ --danger: #ff5c5d; |
|
| 256 |
+ --success: #3acb93; |
|
| 257 |
+ --warning: #ffc24d; |
|
| 258 |
+ |
|
| 259 |
+ --scrim: rgb(0 0 0 / 0.7); |
|
| 24 | 260 |
} |
| 25 | 261 |
} |
| 26 | 262 |
|
| 27 | 263 |
body {
|
| 28 | 264 |
background: var(--background); |
| 29 | 265 |
color: var(--foreground); |
| 30 |
- font-family: Arial, Helvetica, sans-serif; |
|
| 31 | 266 |
} |
+++ components/ui/alert.tsx
... | ... | @@ -0,0 +1,39 @@ |
| 1 | +import type { ReactNode } from "react"; | |
| 2 | + | |
| 3 | +type AlertVariant = "info" | "danger"; | |
| 4 | + | |
| 5 | +const TITLE_TONE_CLASSES: Record<AlertVariant, string> = { | |
| 6 | + info: "text-foreground", | |
| 7 | + danger: "text-danger", | |
| 8 | +}; | |
| 9 | + | |
| 10 | +interface AlertProps { | |
| 11 | + variant?: AlertVariant; | |
| 12 | + title: string; | |
| 13 | + children?: ReactNode; | |
| 14 | + actions?: ReactNode; | |
| 15 | +} | |
| 16 | + | |
| 17 | +/** | |
| 18 | + * 확인/경고 다이얼로그 비주얼(scrim 오버레이 + 패널). | |
| 19 | + * 열림/닫힘 여부는 호출부(feature)가 마운트/언마운트로 제어하고, 포커스 트랩도 | |
| 20 | + * 호출부 책임이다. 이 컴포넌트는 상태를 가지지 않는다. | |
| 21 | + */ | |
| 22 | +export function Alert({ variant = "info", title, children, actions }: AlertProps) { | |
| 23 | + return ( | |
| 24 | + <div className="fixed inset-0 z-50 flex items-center justify-center bg-scrim p-4"> | |
| 25 | + <div | |
| 26 | + role="alertdialog" | |
| 27 | + aria-modal="true" | |
| 28 | + aria-label={title} | |
| 29 | + className="w-full max-w-sm animate-dialog-in rounded-xl bg-surface p-6 shadow-dialog" | |
| 30 | + > | |
| 31 | + <h2 className={`text-title-lg ${TITLE_TONE_CLASSES[variant]}`}>{title}</h2> | |
| 32 | + {children && ( | |
| 33 | + <div className="mt-2 text-body-md text-foreground-muted">{children}</div> | |
| 34 | + )} | |
| 35 | + {actions && <div className="mt-6 flex justify-end gap-2">{actions}</div>} | |
| 36 | + </div> | |
| 37 | + </div> | |
| 38 | + ); | |
| 39 | +} |
+++ components/ui/auth-card.tsx
... | ... | @@ -0,0 +1,29 @@ |
| 1 | +import Image from "next/image"; | |
| 2 | +import type { ReactNode } from "react"; | |
| 3 | + | |
| 4 | +interface AuthCardProps { | |
| 5 | + title?: string; | |
| 6 | + children: ReactNode; | |
| 7 | +} | |
| 8 | + | |
| 9 | +/** primary 배경 전면 위 중앙 카드(로그인 등 인증 화면용). */ | |
| 10 | +export function AuthCard({ title, children }: AuthCardProps) { | |
| 11 | + return ( | |
| 12 | + <div className="flex min-h-screen items-center justify-center bg-primary p-6"> | |
| 13 | + <div className="w-full max-w-sm rounded-2xl bg-surface p-8 shadow-list-card"> | |
| 14 | + <Image | |
| 15 | + src="/images/logo-foxpay.svg" | |
| 16 | + alt="FoxPay" | |
| 17 | + width={170} | |
| 18 | + height={62} | |
| 19 | + className="mx-auto h-10 w-auto" | |
| 20 | + priority | |
| 21 | + /> | |
| 22 | + {title && ( | |
| 23 | + <h1 className="mt-6 text-center text-title-lg text-foreground">{title}</h1> | |
| 24 | + )} | |
| 25 | + <div className="mt-6">{children}</div> | |
| 26 | + </div> | |
| 27 | + </div> | |
| 28 | + ); | |
| 29 | +} |
+++ components/ui/header.tsx
... | ... | @@ -0,0 +1,34 @@ |
| 1 | +import Image from "next/image"; | |
| 2 | +import type { ReactNode } from "react"; | |
| 3 | + | |
| 4 | +interface HeaderProps { | |
| 5 | + title?: string; | |
| 6 | + leading?: ReactNode; | |
| 7 | + /** 우측 슬롯 */ | |
| 8 | + children?: ReactNode; | |
| 9 | + /** 배치(레이아웃) 조정용 클래스 패스스루만 허용 — 디자인 값(색상·타이포·간격 등) 주입 금지. */ | |
| 10 | + className?: string; | |
| 11 | +} | |
| 12 | + | |
| 13 | +/** 상단 헤더 셸 — 로고 + 타이틀 + 우측 슬롯. */ | |
| 14 | +export function Header({ title, leading, children, className }: HeaderProps) { | |
| 15 | + return ( | |
| 16 | + <header | |
| 17 | + className={`flex items-center justify-between gap-4 border-b border-border bg-surface px-6 py-4 ${className ?? ""}`} | |
| 18 | + > | |
| 19 | + <div className="flex items-center gap-3"> | |
| 20 | + {leading} | |
| 21 | + <Image | |
| 22 | + src="/images/logo-foxpay.svg" | |
| 23 | + alt="FoxPay" | |
| 24 | + width={170} | |
| 25 | + height={62} | |
| 26 | + className="h-8 w-auto" | |
| 27 | + priority | |
| 28 | + /> | |
| 29 | + {title && <span className="text-title-md text-foreground">{title}</span>} | |
| 30 | + </div> | |
| 31 | + {children && <div className="flex items-center gap-3">{children}</div>} | |
| 32 | + </header> | |
| 33 | + ); | |
| 34 | +} |
+++ components/ui/spinner.tsx
... | ... | @@ -0,0 +1,38 @@ |
| 1 | +type SpinnerSize = "sm" | "md" | "lg"; | |
| 2 | +type SpinnerTone = "primary" | "inverse" | "muted"; | |
| 3 | + | |
| 4 | +const SIZE_CLASSES: Record<SpinnerSize, string> = { | |
| 5 | + sm: "size-4 border-2", | |
| 6 | + md: "size-6 border-2", | |
| 7 | + lg: "size-8 border-4", | |
| 8 | +}; | |
| 9 | + | |
| 10 | +const TONE_CLASSES: Record<SpinnerTone, string> = { | |
| 11 | + primary: "text-primary", | |
| 12 | + inverse: "text-on-primary", | |
| 13 | + muted: "text-foreground-subtle", | |
| 14 | +}; | |
| 15 | + | |
| 16 | +interface SpinnerProps { | |
| 17 | + size?: SpinnerSize; | |
| 18 | + tone?: SpinnerTone; | |
| 19 | + /** 스크린리더 전용 안내 문구. 기본 '불러오는 중'. */ | |
| 20 | + label?: string; | |
| 21 | +} | |
| 22 | + | |
| 23 | +/** 로딩 인디케이터. 상태 없는 프레젠테이션 컴포넌트 — 표시 여부는 호출부 책임. */ | |
| 24 | +export function Spinner({ | |
| 25 | + size = "md", | |
| 26 | + tone = "primary", | |
| 27 | + label = "불러오는 중", | |
| 28 | +}: SpinnerProps) { | |
| 29 | + return ( | |
| 30 | + <span role="status" className="inline-flex items-center"> | |
| 31 | + <span | |
| 32 | + aria-hidden="true" | |
| 33 | + className={`inline-block animate-spin rounded-full border-current border-t-transparent ${SIZE_CLASSES[size]} ${TONE_CLASSES[tone]}`} | |
| 34 | + /> | |
| 35 | + <span className="sr-only">{label}</span> | |
| 36 | + </span> | |
| 37 | + ); | |
| 38 | +} |
+++ components/ui/splash-screen.tsx
... | ... | @@ -0,0 +1,22 @@ |
| 1 | +import Image from "next/image"; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * 풀스크린 스플래시. 네이티브 스플래시(edupay-app SplashConfig)와 동일하게 | |
| 5 | + * primary 단색 배경 + 백색 로고 240px(logoWidth) 구성을 따른다. | |
| 6 | + * 표시 여부·퇴장 타이밍(fade-out 적용)은 호출부(feature) 책임 — 이 컴포넌트는 | |
| 7 | + * props를 받지 않는다. | |
| 8 | + */ | |
| 9 | +export function SplashScreen() { | |
| 10 | + return ( | |
| 11 | + <div className="fixed inset-0 z-50 flex animate-fade-in items-center justify-center bg-primary"> | |
| 12 | + <Image | |
| 13 | + src="/images/logo-foxpay-white.svg" | |
| 14 | + alt="FoxPay" | |
| 15 | + width={170} | |
| 16 | + height={62} | |
| 17 | + className="h-auto w-60" | |
| 18 | + priority | |
| 19 | + /> | |
| 20 | + </div> | |
| 21 | + ); | |
| 22 | +} |
+++ components/ui/toast.tsx
... | ... | @@ -0,0 +1,30 @@ |
| 1 | +import type { ReactNode } from "react"; | |
| 2 | + | |
| 3 | +type ToastVariant = "info" | "success" | "danger" | "warning"; | |
| 4 | + | |
| 5 | +const ACCENT_CLASSES: Record<ToastVariant, string> = { | |
| 6 | + info: "border-l-primary", | |
| 7 | + success: "border-l-success", | |
| 8 | + danger: "border-l-danger", | |
| 9 | + warning: "border-l-warning", | |
| 10 | +}; | |
| 11 | + | |
| 12 | +interface ToastProps { | |
| 13 | + variant?: ToastVariant; | |
| 14 | + children: ReactNode; | |
| 15 | +} | |
| 16 | + | |
| 17 | +/** | |
| 18 | + * 알림 비주얼(토스트 1개). 위치·스택·표시 타이머는 호출부(feature) 책임 — | |
| 19 | + * 이 컴포넌트는 상태를 가지지 않는다. | |
| 20 | + */ | |
| 21 | +export function Toast({ variant = "info", children }: ToastProps) { | |
| 22 | + return ( | |
| 23 | + <div | |
| 24 | + role="status" | |
| 25 | + className={`animate-toast-in rounded-lg border-l-4 bg-surface px-4 py-3 text-body-md text-foreground shadow-toast ${ACCENT_CLASSES[variant]}`} | |
| 26 | + > | |
| 27 | + {children} | |
| 28 | + </div> | |
| 29 | + ); | |
| 30 | +} |
+++ public/fonts/wanted-sans-bold.ttf
| Binary file is not shown |
+++ public/fonts/wanted-sans-medium.ttf
| Binary file is not shown |
+++ public/fonts/wanted-sans-regular.ttf
| Binary file is not shown |
+++ public/fonts/wanted-sans-semibold.ttf
| Binary file is not shown |
+++ public/images/logo-foxpay-white.svg
... | ... | @@ -0,0 +1,13 @@ |
| 1 | +<svg width="170" height="62" viewBox="0 0 170 62" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 2 | +<g clip-path="url(#clip0_172_26201)"> | |
| 3 | +<path d="M94.2017 12.6853C103.635 12.6853 108.41 16.6355 108.41 24.5263C108.41 32.417 103.635 36.474 94.0934 36.474H84.3848V51.2849H77.9648V12.6853H94.2017ZM84.3749 31.0679H93.6996C96.5551 31.0679 98.6327 30.5244 99.9521 29.5538C101.272 28.525 101.931 26.8459 101.931 24.5263C101.931 22.2066 101.222 20.576 99.9029 19.6055C98.5834 18.5767 96.5058 18.0914 93.7094 18.0914H84.3848V31.0679H84.3749Z" fill="white"/> | |
| 4 | +<path d="M135.498 25.6619C137.25 27.4477 138.127 30.0391 138.127 33.4458V51.2849H132.307V47.6064C131.215 49.0138 129.836 50.0426 128.251 50.7996C126.439 51.6052 124.302 52.0419 121.831 52.0419C118.867 52.0419 116.563 51.2849 114.859 49.829C113.107 48.315 112.23 46.4223 112.23 44.093C112.23 40.958 113.442 38.5219 115.962 36.8428C118.266 35.222 121.506 34.3582 125.671 34.2514L131.874 34.0864V33.0091C131.874 29.3306 129.846 27.5448 125.789 27.5448C124.036 27.5448 122.668 27.8165 121.624 28.4668C120.413 29.1656 119.596 30.2526 119.261 31.7085L113.067 31.2232C113.668 28.1368 115.204 25.8657 117.676 24.468C119.763 23.1675 122.609 22.5754 126.232 22.5754C130.397 22.5754 133.528 23.6042 135.508 25.6619H135.498ZM126.124 38.6869C121.132 38.7937 118.66 40.5213 118.66 43.8794C118.66 44.85 119.044 45.6653 119.921 46.3156C120.797 46.9659 121.9 47.3444 123.268 47.3444C125.681 47.3444 127.709 46.6456 129.354 45.2868C130.998 43.8794 131.874 42.0936 131.874 39.9292V38.5219L126.114 38.6869H126.124Z" fill="white"/> | |
| 5 | +<path d="M148.505 23.3324L156.018 43.2291L163.206 23.3324H170.01L154.649 61.9902H147.954L152.946 50.6345L141.701 23.3324H148.505Z" fill="white"/> | |
| 6 | +<path d="M55.2685 47.1017C55.1011 46.335 55.2488 45.5391 55.6623 44.8694C58.4095 40.4048 59.9948 35.1637 59.9948 29.5636C59.9948 13.2289 46.6036 0.0097057 30.0319 0H30.0023C13.4208 0 -0.0196714 13.2483 2.16131e-05 29.5927C0.0197146 45.9274 13.4208 59.1271 29.9925 59.1271C35.6739 59.1271 40.991 57.5645 45.5204 54.8566C46.0128 54.5654 46.574 54.4101 47.1451 54.4101C47.3617 54.4101 47.5685 54.4296 47.7851 54.4781L55.4556 56.0601C55.5442 56.0795 55.623 56.0892 55.7116 56.0892C56.4501 56.0892 57.0507 55.4098 56.8833 54.6528L55.2783 47.092L55.2685 47.1017ZM41.3357 35.0473C41.3357 35.3287 41.2175 35.5908 41.0009 35.7849L30.2583 45.6944C30.1106 45.8303 29.8743 45.8303 29.7266 45.6944L18.9841 35.7849C18.7675 35.5908 18.6493 35.319 18.6493 35.0473V16.2959C18.6493 16.1018 18.8167 15.9465 19.0235 15.9465H20.0672C20.3724 15.9465 20.658 16.0532 20.8648 16.257L29.7168 24.4195C29.8645 24.5554 30.1008 24.5554 30.2485 24.4195L39.1005 16.257C39.3171 16.0629 39.6027 15.9465 39.8981 15.9465H40.9418C41.1486 15.9465 41.316 16.1018 41.316 16.2959V35.0473H41.3357Z" fill="white"/> | |
| 7 | +</g> | |
| 8 | +<defs> | |
| 9 | +<clipPath id="clip0_172_26201"> | |
| 10 | +<rect width="170" height="62" fill="white"/> | |
| 11 | +</clipPath> | |
| 12 | +</defs> | |
| 13 | +</svg> |
+++ public/images/logo-foxpay.svg
... | ... | @@ -0,0 +1,13 @@ |
| 1 | +<svg width="170" height="62" viewBox="0 0 170 62" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| 2 | +<g clip-path="url(#clip0_172_26200)"> | |
| 3 | +<path d="M94.2017 12.6853C103.635 12.6853 108.41 16.6355 108.41 24.5263C108.41 32.417 103.635 36.474 94.0934 36.474H84.3848V51.2849H77.9648V12.6853H94.2017ZM84.3749 31.0679H93.6996C96.5551 31.0679 98.6327 30.5244 99.9521 29.5538C101.272 28.525 101.931 26.8459 101.931 24.5263C101.931 22.2066 101.222 20.576 99.9029 19.6055C98.5834 18.5767 96.5058 18.0914 93.7094 18.0914H84.3848V31.0679H84.3749Z" fill="#1D3661"/> | |
| 4 | +<path d="M135.498 25.6619C137.25 27.4477 138.127 30.0391 138.127 33.4458V51.2849H132.307V47.6064C131.215 49.0138 129.836 50.0426 128.251 50.7996C126.439 51.6052 124.302 52.0419 121.831 52.0419C118.867 52.0419 116.563 51.2849 114.859 49.829C113.107 48.315 112.23 46.4223 112.23 44.093C112.23 40.958 113.442 38.5219 115.962 36.8428C118.266 35.222 121.506 34.3582 125.671 34.2514L131.874 34.0864V33.0091C131.874 29.3306 129.846 27.5448 125.789 27.5448C124.036 27.5448 122.668 27.8165 121.624 28.4668C120.413 29.1656 119.596 30.2526 119.261 31.7085L113.067 31.2232C113.668 28.1368 115.204 25.8657 117.676 24.468C119.763 23.1675 122.609 22.5754 126.232 22.5754C130.397 22.5754 133.528 23.6042 135.508 25.6619H135.498ZM126.124 38.6869C121.132 38.7937 118.66 40.5213 118.66 43.8794C118.66 44.85 119.044 45.6653 119.921 46.3156C120.797 46.9659 121.9 47.3444 123.268 47.3444C125.681 47.3444 127.709 46.6456 129.354 45.2868C130.998 43.8794 131.874 42.0936 131.874 39.9292V38.5219L126.114 38.6869H126.124Z" fill="#1D3661"/> | |
| 5 | +<path d="M148.505 23.3324L156.018 43.2291L163.206 23.3324H170.01L154.649 61.9902H147.954L152.946 50.6345L141.701 23.3324H148.505Z" fill="#1D3661"/> | |
| 6 | +<path d="M55.2685 47.1017C55.1011 46.335 55.2488 45.5391 55.6623 44.8694C58.4095 40.4048 59.9948 35.1637 59.9948 29.5636C59.9948 13.2289 46.6036 0.0097057 30.0319 0H30.0023C13.4208 0 -0.0196714 13.2483 2.16131e-05 29.5927C0.0197146 45.9274 13.4208 59.1271 29.9925 59.1271C35.6739 59.1271 40.991 57.5645 45.5204 54.8566C46.0128 54.5654 46.574 54.4101 47.1451 54.4101C47.3617 54.4101 47.5685 54.4296 47.7851 54.4781L55.4556 56.0601C55.5442 56.0795 55.623 56.0892 55.7116 56.0892C56.4501 56.0892 57.0507 55.4098 56.8833 54.6528L55.2783 47.092L55.2685 47.1017ZM41.3357 35.0473C41.3357 35.3287 41.2175 35.5908 41.0009 35.7849L30.2583 45.6944C30.1106 45.8303 29.8743 45.8303 29.7266 45.6944L18.9841 35.7849C18.7675 35.5908 18.6493 35.319 18.6493 35.0473V16.2959C18.6493 16.1018 18.8167 15.9465 19.0235 15.9465H20.0672C20.3724 15.9465 20.658 16.0532 20.8648 16.257L29.7168 24.4195C29.8645 24.5554 30.1008 24.5554 30.2485 24.4195L39.1005 16.257C39.3171 16.0629 39.6027 15.9465 39.8981 15.9465H40.9418C41.1486 15.9465 41.316 16.1018 41.316 16.2959V35.0473H41.3357Z" fill="#655DC6"/> | |
| 7 | +</g> | |
| 8 | +<defs> | |
| 9 | +<clipPath id="clip0_172_26200"> | |
| 10 | +<rect width="170" height="62" fill="white"/> | |
| 11 | +</clipPath> | |
| 12 | +</defs> | |
| 13 | +</svg> |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?