@korioinc/next-core 2.0.15 → 2.0.17
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/dist/i18n/routing.d.ts +8 -4
- package/dist/i18n/routing.d.ts.map +1 -1
- package/dist/i18n/routing.js +80 -38
- package/dist/styles/AGENTS.md +183 -0
- package/dist/styles/globals.css +61 -0
- package/package.json +6 -6
package/dist/i18n/routing.d.ts
CHANGED
|
@@ -18,8 +18,12 @@ export declare function getPathname({ locale, href }: {
|
|
|
18
18
|
}): string;
|
|
19
19
|
export declare function getLanguageAlternates(pathname?: string): Record<string, string>;
|
|
20
20
|
export declare function getCurrentPathname(): Promise<string>;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
export declare function getCurrentSearchParams(): Promise<URLSearchParams>;
|
|
22
|
+
export declare function getDefaultPathname(): Promise<string>;
|
|
23
|
+
export declare function getCurrentLanguageAlternates(): Promise<Record<string, string>>;
|
|
24
|
+
type FullUrlParams = Record<string, string | string[] | undefined> | URLSearchParams | ReadonlyURLSearchParams | undefined;
|
|
25
|
+
export declare function getFullUrlByPathname(pathname: string, params?: FullUrlParams): string;
|
|
26
|
+
export declare function getFullUrl(params?: FullUrlParams): Promise<string>;
|
|
27
|
+
export declare function getFullUrlWithCurrentParams(): Promise<string>;
|
|
28
|
+
export {};
|
|
25
29
|
//# sourceMappingURL=routing.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/i18n/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/i18n/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsC7D;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,EAAE,CAErC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAyDD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,yBAyEvD;AAED,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,OAAO,GAAG,MAAM,CAShE;AAED,wBAAgB,eAAe,CAAC,cAAc,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAkB9F;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAoBvG;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,UAY7E;AAsBD,wBAAgB,qBAAqB,CAAC,QAAQ,GAAE,MAAY,0BAwB3D;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAK1D;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,eAAe,CAAC,CAMvE;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAU1D;AAED,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAIpF;AAED,KAAK,aAAa,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAC7C,eAAe,GACf,uBAAuB,GACvB,SAAS,CAAC;AAiDd,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAErF;AAED,wBAAsB,UAAU,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAIxE;AAED,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,MAAM,CAAC,CAKnE"}
|
package/dist/i18n/routing.js
CHANGED
|
@@ -18,6 +18,13 @@ const DEFAULT_FALLBACK = () => {
|
|
|
18
18
|
};
|
|
19
19
|
const LOCALE_COOKIE_NAME = 'NEXT_LOCALE';
|
|
20
20
|
const LOCALE_PREFIX = 'as-needed';
|
|
21
|
+
const PATHNAME_HEADER_NAME = 'x-pathname';
|
|
22
|
+
const SEARCH_PARAMS_HEADER_NAME = 'x-search-params';
|
|
23
|
+
const setRoutingHeaders = (response, pathname, search) => {
|
|
24
|
+
response.headers.set(PATHNAME_HEADER_NAME, pathname);
|
|
25
|
+
response.headers.set(SEARCH_PARAMS_HEADER_NAME, search.replace(/^\?/, ''));
|
|
26
|
+
return response;
|
|
27
|
+
};
|
|
21
28
|
/**
|
|
22
29
|
* Get all available locales
|
|
23
30
|
*/
|
|
@@ -54,8 +61,7 @@ const redirectWithoutLocale = (request, pathname, locale, shouldSetCookie = fals
|
|
|
54
61
|
const url = new URL(newPathname, request.url);
|
|
55
62
|
url.search = request.nextUrl.search;
|
|
56
63
|
url.hash = request.nextUrl.hash;
|
|
57
|
-
const response = NextResponse.redirect(url, 302);
|
|
58
|
-
response.headers.set('x-pathname', newPathname);
|
|
64
|
+
const response = setRoutingHeaders(NextResponse.redirect(url, 302), newPathname, request.nextUrl.search);
|
|
59
65
|
if (shouldSetCookie) {
|
|
60
66
|
setCookie(response, locale);
|
|
61
67
|
}
|
|
@@ -68,8 +74,7 @@ const rewriteWithLocale = (request, pathname, locale) => {
|
|
|
68
74
|
const internalUrl = new URL(`/${locale}${pathname}`, request.url);
|
|
69
75
|
internalUrl.search = request.nextUrl.search;
|
|
70
76
|
internalUrl.hash = request.nextUrl.hash;
|
|
71
|
-
const response = NextResponse.rewrite(internalUrl);
|
|
72
|
-
response.headers.set('x-pathname', pathname);
|
|
77
|
+
const response = setRoutingHeaders(NextResponse.rewrite(internalUrl), pathname, request.nextUrl.search);
|
|
73
78
|
return response;
|
|
74
79
|
};
|
|
75
80
|
export function handleLocaleRouting(request) {
|
|
@@ -89,8 +94,7 @@ export function handleLocaleRouting(request) {
|
|
|
89
94
|
// 2. URL에 locale이 포함된 경우
|
|
90
95
|
if (pathnameHasLocale) {
|
|
91
96
|
// 이미 path에 locale이 있을 때
|
|
92
|
-
const response = NextResponse.next();
|
|
93
|
-
response.headers.set('x-pathname', pathname);
|
|
97
|
+
const response = setRoutingHeaders(NextResponse.next(), pathname, request.nextUrl.search);
|
|
94
98
|
// 쿠키 값이 path locale과 다를 경우
|
|
95
99
|
if (localeCookie?.value !== pathLocale) {
|
|
96
100
|
// 기본 locale인 경우 URL에서 제거
|
|
@@ -125,8 +129,7 @@ export function handleLocaleRouting(request) {
|
|
|
125
129
|
// 다른 locale은 URL에 표시
|
|
126
130
|
const newPathname = getChangeLocalePath(pathname, defaultFallback, locale);
|
|
127
131
|
request.nextUrl.pathname = newPathname;
|
|
128
|
-
const response = NextResponse.redirect(request.nextUrl);
|
|
129
|
-
response.headers.set('x-pathname', newPathname);
|
|
132
|
+
const response = setRoutingHeaders(NextResponse.redirect(request.nextUrl), newPathname, request.nextUrl.search);
|
|
130
133
|
// 쿠키가 없는 경우 설정
|
|
131
134
|
if (localeCookie?.value === undefined) {
|
|
132
135
|
setCookie(response, locale);
|
|
@@ -189,18 +192,24 @@ export function getPathname({ locale, href }) {
|
|
|
189
192
|
}
|
|
190
193
|
return href.startsWith('/') ? `/${locale}${href}` : `/${locale}/${href}`;
|
|
191
194
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// Remove any existing locale from the pathname to get the base path
|
|
195
|
-
let basePath = pathname;
|
|
195
|
+
const getBasePathWithoutLocale = (pathname = '/') => {
|
|
196
|
+
let basePath = pathname || '/';
|
|
196
197
|
locales.forEach((locale) => {
|
|
197
198
|
const localePattern = new RegExp(`^/${locale}(/|$)`);
|
|
198
199
|
if (localePattern.test(basePath)) {
|
|
199
200
|
basePath = basePath.replace(localePattern, '/');
|
|
200
201
|
}
|
|
201
202
|
});
|
|
202
|
-
// Normalize path
|
|
203
|
+
// Normalize path and ensure root fallback
|
|
203
204
|
basePath = basePath.replace(/\/+/g, '/');
|
|
205
|
+
if (!basePath.startsWith('/')) {
|
|
206
|
+
basePath = `/${basePath}`;
|
|
207
|
+
}
|
|
208
|
+
return basePath || '/';
|
|
209
|
+
};
|
|
210
|
+
export function getLanguageAlternates(pathname = '/') {
|
|
211
|
+
const defaultFallback = DEFAULT_FALLBACK();
|
|
212
|
+
const basePath = getBasePathWithoutLocale(pathname);
|
|
204
213
|
const alternates = {};
|
|
205
214
|
// Set x-default based on LOCALE_PREFIX and defaultFallback
|
|
206
215
|
if (LOCALE_PREFIX === 'as-needed') {
|
|
@@ -224,40 +233,73 @@ export function getLanguageAlternates(pathname = '/') {
|
|
|
224
233
|
export async function getCurrentPathname() {
|
|
225
234
|
const { headers } = await import('next/headers');
|
|
226
235
|
const headersList = await headers();
|
|
227
|
-
return headersList.get(
|
|
236
|
+
return headersList.get(PATHNAME_HEADER_NAME) || '';
|
|
228
237
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
238
|
+
export async function getCurrentSearchParams() {
|
|
239
|
+
const { headers } = await import('next/headers');
|
|
240
|
+
const headersList = await headers();
|
|
241
|
+
const rawSearchParams = headersList.get(SEARCH_PARAMS_HEADER_NAME) || '';
|
|
242
|
+
return new URLSearchParams(rawSearchParams);
|
|
243
|
+
}
|
|
244
|
+
export async function getDefaultPathname() {
|
|
245
|
+
const pathname = await getCurrentPathname();
|
|
246
|
+
const basePath = getBasePathWithoutLocale(pathname);
|
|
247
|
+
const defaultFallback = DEFAULT_FALLBACK();
|
|
248
|
+
if (LOCALE_PREFIX === 'as-needed') {
|
|
249
|
+
return basePath;
|
|
236
250
|
}
|
|
251
|
+
return `/${defaultFallback}${basePath === '/' ? '' : basePath}`;
|
|
252
|
+
}
|
|
253
|
+
export async function getCurrentLanguageAlternates() {
|
|
254
|
+
const pathname = await getCurrentPathname();
|
|
255
|
+
return getLanguageAlternates(pathname);
|
|
256
|
+
}
|
|
257
|
+
const isSearchParamsLike = (params) => {
|
|
258
|
+
return typeof params.get === 'function' && typeof params.forEach === 'function';
|
|
259
|
+
};
|
|
260
|
+
const toSearchParams = (params) => {
|
|
237
261
|
const searchParams = new URLSearchParams();
|
|
238
|
-
|
|
239
|
-
|
|
262
|
+
if (!params) {
|
|
263
|
+
return searchParams;
|
|
264
|
+
}
|
|
265
|
+
if (isSearchParamsLike(params)) {
|
|
240
266
|
params.forEach((value, key) => {
|
|
241
|
-
// locale 파라미터는 제외
|
|
242
267
|
if (key !== 'locale') {
|
|
243
268
|
searchParams.append(key, value);
|
|
244
269
|
}
|
|
245
270
|
});
|
|
271
|
+
return searchParams;
|
|
246
272
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
273
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
274
|
+
if (value === undefined)
|
|
275
|
+
return;
|
|
276
|
+
if (Array.isArray(value)) {
|
|
277
|
+
value.forEach((v) => searchParams.append(key, v));
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
searchParams.append(key, value);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
return searchParams;
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
286
|
+
* pathname과 params를 조합하여 전체 URL을 생성하는 함수
|
|
287
|
+
*/
|
|
288
|
+
const buildFullUrl = (pathname, params) => {
|
|
289
|
+
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || '';
|
|
290
|
+
const searchParams = toSearchParams(params);
|
|
261
291
|
const queryString = searchParams.toString();
|
|
262
292
|
return `${baseUrl}${pathname}${queryString ? `?${queryString}` : ''}`;
|
|
293
|
+
};
|
|
294
|
+
export function getFullUrlByPathname(pathname, params) {
|
|
295
|
+
return buildFullUrl(pathname, params);
|
|
296
|
+
}
|
|
297
|
+
export async function getFullUrl(params) {
|
|
298
|
+
const pathname = await getCurrentPathname();
|
|
299
|
+
return buildFullUrl(pathname, params);
|
|
300
|
+
}
|
|
301
|
+
export async function getFullUrlWithCurrentParams() {
|
|
302
|
+
const pathname = await getCurrentPathname();
|
|
303
|
+
const currentParams = await getCurrentSearchParams();
|
|
304
|
+
return buildFullUrl(pathname, currentParams);
|
|
263
305
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Glass 토큰/유틸 역할 정리 (modal 사용 기준 + globals.css 기반)
|
|
2
|
+
|
|
3
|
+
이 문서는 **모달에서 실제 사용한 조합**과 `packages/core/src/styles/globals.css`의 정의를 확인한 뒤, glass 관련 토큰/유틸리티의 역할과 사용 기준을 정리한 것입니다.
|
|
4
|
+
|
|
5
|
+
## 1) 모달 사용 기준 (참조: `/apps/web/src/app/[locale]/modal/_components/modal.tsx`)
|
|
6
|
+
- **오버레이**: `glass` (배경을 얇게 덮고 유리감만 부여)
|
|
7
|
+
- **모달 패널**: `glass-tinted glass-backdrop border-glass-strong`
|
|
8
|
+
- **모달 내부 컨테이너**: `border-glass-strong` + `bg-white/60`(다크는 `dark:bg-slate-900/80`) + `backdrop-blur` 계열
|
|
9
|
+
- **입력/버튼**: `glass border-glass-strong` + hover 시 `hover:bg-glass-tinted`
|
|
10
|
+
|
|
11
|
+
## 2) globals.css 정의 기반 역할 요약
|
|
12
|
+
|
|
13
|
+
### 2.1 토큰(커스텀 프로퍼티)
|
|
14
|
+
`--glass*` 계열은 라이트/다크 각각 별도 값이 있으며, 공통적으로 다음 역할을 가집니다.
|
|
15
|
+
- `--glass`: 유리 레이어 기본 색 (투명도 포함)
|
|
16
|
+
- `--glass-tinted`: 유리의 색 농도 강화 버전
|
|
17
|
+
- `--glass-glow`: 유리의 발광 느낌을 위한 색
|
|
18
|
+
- `--glass-surface`: 실제 표면 색(배경 그라데이션 밑바탕)
|
|
19
|
+
- `--glass-surface-tinted`: 표면 색의 틴트 버전
|
|
20
|
+
- `--glass-border`: 유리 테두리 강도
|
|
21
|
+
- `--glass-border-subtle`: 유리 테두리 약한 버전
|
|
22
|
+
- `--glass-shine`: 유리 하이라이트
|
|
23
|
+
- `--glass-shine-subtle`: 유리 하이라이트 약한 버전
|
|
24
|
+
- `--glass-shadow-color`: 유리 그림자 컬러
|
|
25
|
+
- `--glass-shadow-ambient`: 유리 앰비언트 그림자
|
|
26
|
+
- `--glass-blur`: 백드롭 블러 강도 (기본 8px)
|
|
27
|
+
|
|
28
|
+
### 2.2 유틸리티(클래스)
|
|
29
|
+
globals.css 내 `@utility` 정의 기준
|
|
30
|
+
|
|
31
|
+
#### `glass`
|
|
32
|
+
- **역할**: 기본 유리 표면. 그라데이션 + 그림자 + 내부 하이라이트 포함.
|
|
33
|
+
- **대상**: 카드, 패널, 버튼, 인풋 등 “표면” 요소.
|
|
34
|
+
|
|
35
|
+
#### `glass-tinted`
|
|
36
|
+
- **역할**: `glass`와 동일 효과 + 틴트된 표면(`--glass-surface-tinted`).
|
|
37
|
+
- **대상**: 모달 패널, 강조 카드.
|
|
38
|
+
|
|
39
|
+
#### `glass-glow`
|
|
40
|
+
- **역할**: `glass`와 동일 효과 + 발광 표면(`--glass-surface-glow`).
|
|
41
|
+
- **대상**: 강조 배지, 강조 카드.
|
|
42
|
+
|
|
43
|
+
#### `glass-backdrop`
|
|
44
|
+
- **역할**: 배경 블러 + 채도 증가.
|
|
45
|
+
- **대상**: 모달 패널, 오버레이 같이 **뒤 배경이 비쳐야 하는 영역**.
|
|
46
|
+
|
|
47
|
+
#### `glass-overlay`
|
|
48
|
+
- **역할**: 기존 `bg-*` 색상을 유지한 채 유리 질감만 얹는다.
|
|
49
|
+
- **대상**: 색상 버튼/배지/태그 같이 `bg-*`가 있는 요소.
|
|
50
|
+
|
|
51
|
+
#### `border-glass-soft`
|
|
52
|
+
- **역할**: 은은한 유리 테두리 + 내부 얕은 하이라이트.
|
|
53
|
+
- **대상**: 보조 카드, 내부 블록.
|
|
54
|
+
|
|
55
|
+
#### `border-glass-strong`
|
|
56
|
+
- **역할**: 더 선명한 유리 테두리 + 내부 하이라이트.
|
|
57
|
+
- **대상**: 모달 패널, 버튼, 입력처럼 **경계가 분명해야 하는 요소**.
|
|
58
|
+
|
|
59
|
+
## 3) 사용 규칙 (짧은 체크리스트)
|
|
60
|
+
- 유리 표면에는 `border-glass-*`를 **필요할 때만** 붙여 경계를 잡는다.
|
|
61
|
+
- 블러가 필요한 곳에만 `glass-backdrop`을 붙인다.
|
|
62
|
+
- 강조 패널에는 `glass-tinted`, 일반 카드에는 `glass`가 기본.
|
|
63
|
+
- 다크 모드에서는 텍스트/placeholder 대비를 추가로 보정한다 (`dark:*`).
|
|
64
|
+
- **색상 버튼/배지**는 `bg-*` + `glass-overlay` 조합을 기본으로 사용한다.
|
|
65
|
+
|
|
66
|
+
## 4) 적용 예시 (모달/입력/버튼)
|
|
67
|
+
|
|
68
|
+
### 4.1 모달 오버레이
|
|
69
|
+
```tsx
|
|
70
|
+
<div className="glass fixed inset-0" />
|
|
71
|
+
```
|
|
72
|
+
- **의도**: 배경을 얇게 덮고 유리감만 부여.
|
|
73
|
+
|
|
74
|
+
### 4.2 모달 패널
|
|
75
|
+
```tsx
|
|
76
|
+
<Dialog.Panel className="glass-tinted glass-backdrop border-glass-strong rounded-2xl border p-8 shadow-2xl">
|
|
77
|
+
...
|
|
78
|
+
</Dialog.Panel>
|
|
79
|
+
```
|
|
80
|
+
- **의도**: 패널 자체는 틴트 유리 + 배경 블러 + 강한 경계.
|
|
81
|
+
|
|
82
|
+
### 4.3 모달 내부 컨테이너(본문 영역)
|
|
83
|
+
```tsx
|
|
84
|
+
<div className="rounded-2xl border border-white/40 bg-white/60 p-6 backdrop-blur-xl dark:border-slate-700/70 dark:bg-slate-900/80">
|
|
85
|
+
...
|
|
86
|
+
</div>
|
|
87
|
+
```
|
|
88
|
+
- **의도**: 내부 콘텐츠는 더 밝게, 다크 모드 대비 보정.
|
|
89
|
+
|
|
90
|
+
### 4.4 입력 필드
|
|
91
|
+
```tsx
|
|
92
|
+
<input
|
|
93
|
+
className="glass border-glass-strong rounded-lg border px-3 py-2 text-sm shadow-sm
|
|
94
|
+
focus-visible:ring-2 focus-visible:ring-slate-900/20
|
|
95
|
+
dark:placeholder:text-slate-400 dark:focus-visible:ring-white/20"
|
|
96
|
+
/>
|
|
97
|
+
```
|
|
98
|
+
- **의도**: 유리 표면 + 강한 경계 + 포커스 링 보정.
|
|
99
|
+
|
|
100
|
+
### 4.5 버튼
|
|
101
|
+
```tsx
|
|
102
|
+
<button className="glass border-glass-strong hover:bg-glass-tinted rounded-lg px-5 py-2 text-sm">
|
|
103
|
+
...
|
|
104
|
+
</button>
|
|
105
|
+
```
|
|
106
|
+
- **의도**: 기본은 glass, hover에서 tinted로 강조.
|
|
107
|
+
|
|
108
|
+
### 4.6 색상 버튼 (glass-overlay)
|
|
109
|
+
```tsx
|
|
110
|
+
<button className="glass-overlay bg-emerald-400 text-slate-900 rounded-lg px-5 py-2 text-sm">
|
|
111
|
+
...
|
|
112
|
+
</button>
|
|
113
|
+
```
|
|
114
|
+
- **의도**: `bg-*`를 유지하면서 glass 질감만 얹는다.
|
|
115
|
+
|
|
116
|
+
### 4.7 탭 선택 상태 (glass-overlay)
|
|
117
|
+
```tsx
|
|
118
|
+
<div className="flex gap-2">
|
|
119
|
+
<button className="glass border-glass-strong text-foreground rounded-full px-4 py-2 text-sm">
|
|
120
|
+
콘텐츠 작성
|
|
121
|
+
</button>
|
|
122
|
+
<button className="glass-overlay bg-slate-900 text-white rounded-full px-4 py-2 text-sm shadow-md">
|
|
123
|
+
렌더링 미리보기 (선택됨)
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
```
|
|
127
|
+
- **의도**: 탭 선택 상태에서 `bg-*`를 유지한 채 glass 질감을 추가한다.
|
|
128
|
+
|
|
129
|
+
### 4.8 기본 Solid 버튼 (non-glass)
|
|
130
|
+
```tsx
|
|
131
|
+
<button className="bg-primary hover:bg-primary text-primary-foreground rounded-lg px-5 py-2 text-sm font-semibold shadow-lg transition">
|
|
132
|
+
...
|
|
133
|
+
</button>
|
|
134
|
+
```
|
|
135
|
+
- **의도**: 가장 기본적인 CTA 버튼. glass가 필요 없을 때 사용.
|
|
136
|
+
|
|
137
|
+
### 4.9 알림/에러 메시지 (glass + 컬러 배경)
|
|
138
|
+
```tsx
|
|
139
|
+
<div className="glass border-glass-strong rounded-2xl border border-rose-200/80 bg-rose-50/70 px-4 py-3 text-sm text-rose-600 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-200">
|
|
140
|
+
...
|
|
141
|
+
</div>
|
|
142
|
+
```
|
|
143
|
+
- **의도**: 유리 표면 위에 컬러 배경을 얹어 메시지 톤을 유지한다.
|
|
144
|
+
|
|
145
|
+
## 5) 적용 순서 가이드
|
|
146
|
+
1. **표면 선택**: `glass` 또는 `glass-tinted`
|
|
147
|
+
2. **경계 부여**: `border-glass-strong` 또는 `border-glass-soft`
|
|
148
|
+
3. **배경 블러 필요 여부**: 필요하면 `glass-backdrop`
|
|
149
|
+
4. **다크 보정**: `dark:*`로 텍스트/placeholder 대비 조정
|
|
150
|
+
|
|
151
|
+
## 6) 추가 예시 (카드/리스트/배지)
|
|
152
|
+
|
|
153
|
+
### 6.1 기본 카드
|
|
154
|
+
```tsx
|
|
155
|
+
<div className="glass border-glass-soft rounded-2xl p-6 shadow-md">
|
|
156
|
+
...
|
|
157
|
+
</div>
|
|
158
|
+
```
|
|
159
|
+
- **의도**: 보조 영역에 은은한 유리감.
|
|
160
|
+
|
|
161
|
+
### 6.2 강조 카드 (CTA)
|
|
162
|
+
```tsx
|
|
163
|
+
<div className="glass-tinted border-glass-strong rounded-2xl p-6 shadow-xl">
|
|
164
|
+
...
|
|
165
|
+
</div>
|
|
166
|
+
```
|
|
167
|
+
- **의도**: 시선이 가야 하는 블록에 강한 대비.
|
|
168
|
+
|
|
169
|
+
### 6.3 리스트 아이템
|
|
170
|
+
```tsx
|
|
171
|
+
<li className="glass border-glass-strong rounded-lg p-3 shadow">
|
|
172
|
+
...
|
|
173
|
+
</li>
|
|
174
|
+
```
|
|
175
|
+
- **의도**: 반복 항목에 동일한 유리 표면 제공.
|
|
176
|
+
|
|
177
|
+
### 6.4 배지/태그
|
|
178
|
+
```tsx
|
|
179
|
+
<span className="glass border-glass-strong rounded-full px-3 py-1 text-xs font-semibold">
|
|
180
|
+
NEW
|
|
181
|
+
</span>
|
|
182
|
+
```
|
|
183
|
+
- **의도**: 작은 요소에도 유리감 유지.
|
package/dist/styles/globals.css
CHANGED
|
@@ -296,6 +296,57 @@
|
|
|
296
296
|
inset 0 1px 0 var(--glass-shine-subtle);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
@utility glass-overlay {
|
|
300
|
+
backdrop-filter: blur(var(--glass-blur)) saturate(1.15);
|
|
301
|
+
background-image:
|
|
302
|
+
linear-gradient(
|
|
303
|
+
135deg,
|
|
304
|
+
color-mix(in oklab, white 30%, transparent) 0%,
|
|
305
|
+
color-mix(in oklab, white 18%, transparent) 30%,
|
|
306
|
+
color-mix(in oklab, white 8%, transparent) 70%,
|
|
307
|
+
color-mix(in oklab, white 22%, transparent) 100%
|
|
308
|
+
),
|
|
309
|
+
radial-gradient(
|
|
310
|
+
120% 120% at 18% 0%,
|
|
311
|
+
color-mix(in oklab, white 26%, transparent) 0%,
|
|
312
|
+
transparent 48%
|
|
313
|
+
);
|
|
314
|
+
box-shadow:
|
|
315
|
+
0 1px 2px color-mix(in oklab, var(--glass-shadow-ambient) 60%, transparent),
|
|
316
|
+
0 6px 12px color-mix(in oklab, var(--glass-shadow-color) 55%, transparent),
|
|
317
|
+
inset 0 1px 0 color-mix(in oklab, white 38%, transparent),
|
|
318
|
+
inset 0 -1px 0 color-mix(in oklab, black 10%, transparent);
|
|
319
|
+
filter: saturate(1.04) brightness(1.03);
|
|
320
|
+
|
|
321
|
+
.dark & {
|
|
322
|
+
background-image:
|
|
323
|
+
linear-gradient(
|
|
324
|
+
180deg,
|
|
325
|
+
color-mix(in oklab, black 22%, transparent) 0%,
|
|
326
|
+
color-mix(in oklab, black 16%, transparent) 100%
|
|
327
|
+
),
|
|
328
|
+
linear-gradient(
|
|
329
|
+
135deg,
|
|
330
|
+
color-mix(in oklab, white 28%, transparent) 0%,
|
|
331
|
+
color-mix(in oklab, white 16%, transparent) 30%,
|
|
332
|
+
color-mix(in oklab, white 7%, transparent) 70%,
|
|
333
|
+
color-mix(in oklab, white 20%, transparent) 100%
|
|
334
|
+
),
|
|
335
|
+
radial-gradient(
|
|
336
|
+
120% 120% at 18% 0%,
|
|
337
|
+
color-mix(in oklab, white 22%, transparent) 0%,
|
|
338
|
+
transparent 44%
|
|
339
|
+
);
|
|
340
|
+
background-blend-mode: multiply, normal, normal;
|
|
341
|
+
box-shadow:
|
|
342
|
+
0 2px 6px color-mix(in oklab, var(--glass-shadow-ambient) 65%, transparent),
|
|
343
|
+
0 10px 18px color-mix(in oklab, var(--glass-shadow-color) 65%, transparent),
|
|
344
|
+
inset 0 1px 0 color-mix(in oklab, white 24%, transparent),
|
|
345
|
+
inset 0 -1px 0 color-mix(in oklab, black 18%, transparent);
|
|
346
|
+
filter: saturate(1.04) brightness(1.03);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
299
350
|
@utility glass-tinted {
|
|
300
351
|
--glass-surface-slot: var(--glass-surface-tinted);
|
|
301
352
|
@apply glass;
|
|
@@ -325,6 +376,11 @@
|
|
|
325
376
|
border-radius: inherit;
|
|
326
377
|
pointer-events: none;
|
|
327
378
|
box-shadow: inset 0 1px 0 var(--glass-shine-subtle);
|
|
379
|
+
transform: translateY(-0.3px);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.dark &::after {
|
|
383
|
+
transform: translateY(0);
|
|
328
384
|
}
|
|
329
385
|
}
|
|
330
386
|
|
|
@@ -343,6 +399,11 @@
|
|
|
343
399
|
border-radius: inherit;
|
|
344
400
|
pointer-events: none;
|
|
345
401
|
box-shadow: inset 0 1px 0 var(--glass-shine-subtle);
|
|
402
|
+
transform: translateY(-0.3px);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.dark &::after {
|
|
406
|
+
transform: translateY(0);
|
|
346
407
|
}
|
|
347
408
|
}
|
|
348
409
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@korioinc/next-core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./ads": {
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
"@lingui/react": "^5.9.0",
|
|
69
69
|
"@tailwindcss/typography": "^0.5.19",
|
|
70
70
|
"@types/negotiator": "^0.6.4",
|
|
71
|
-
"@types/node": "^25.
|
|
72
|
-
"@types/react": "^19.2.
|
|
71
|
+
"@types/node": "^25.2.1",
|
|
72
|
+
"@types/react": "^19.2.13",
|
|
73
73
|
"@types/react-dom": "^19.2.3",
|
|
74
74
|
"eslint": "^9.39.2",
|
|
75
75
|
"next-themes": "^0.4.6",
|
|
@@ -79,11 +79,11 @@
|
|
|
79
79
|
"tsc-alias": "^1.8.16",
|
|
80
80
|
"tw-animate-css": "^1.4.0",
|
|
81
81
|
"typescript": "^5.9.3",
|
|
82
|
-
"@korioinc/next-configs": "2.0.
|
|
82
|
+
"@korioinc/next-configs": "2.0.17"
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"@floating-ui/react": "^0.27.17",
|
|
86
|
-
"@formatjs/intl-localematcher": "^0.
|
|
86
|
+
"@formatjs/intl-localematcher": "^0.8.1",
|
|
87
87
|
"clsx": "^2.1.1",
|
|
88
88
|
"cookies-next": "^6.1.1",
|
|
89
89
|
"cosmiconfig": "^9.0.0",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"schema-dts": "^1.1.5",
|
|
95
95
|
"tailwind-merge": "^3.4.0",
|
|
96
96
|
"valtio": "^2.3.0",
|
|
97
|
-
"@korioinc/next-conf": "2.0.
|
|
97
|
+
"@korioinc/next-conf": "2.0.17"
|
|
98
98
|
},
|
|
99
99
|
"publishConfig": {
|
|
100
100
|
"access": "public",
|