@korioinc/next-core 2.0.47 → 2.0.49

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.
@@ -53,5 +53,5 @@ export default function LocaleSwitcher({ languages } = {}) {
53
53
  if (!currentLanguage) {
54
54
  return null;
55
55
  }
56
- return (_jsxs(Menu, { as: 'div', className: 'relative', children: [_jsx(MenuButton, { className: 'border-glass-strong bg-glass hover:bg-glass-tinted relative inline-flex h-9 w-9 items-center justify-center rounded-md border transition-colors', children: currentLanguage.flag }), _jsx(Transition, { as: Fragment, enter: 'transition ease-out duration-100', enterFrom: 'transform opacity-0 scale-95', enterTo: 'transform opacity-100 scale-100', leave: 'transition ease-in duration-75', leaveFrom: 'transform opacity-100 scale-100', leaveTo: 'transform opacity-0 scale-95', children: _jsx(MenuItems, { anchor: 'bottom end', className: 'glass-tinted glass-backdrop border-glass-strong z-100 w-48 rounded-md border ring-1 ring-black/5 [--anchor-gap:4px] focus:outline-none', children: _jsx("div", { className: 'py-1', children: availableLanguages.map((language) => (_jsx(MenuItem, { children: _jsxs("button", { onClick: () => changeLanguage(language.code), className: 'data-[focus]:bg-glass-tinted text-foreground group flex w-full items-center gap-2 px-3 py-2 text-sm transition-colors', children: [language.flag, _jsx("span", { className: 'whitespace-nowrap', children: language.name }), currentLocale === language.code && (_jsx("svg", { className: 'ml-auto h-4 w-4', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24', children: _jsx("path", { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M5 13l4 4L19 7' }) }))] }) }, language.code))) }) }) })] }));
56
+ return (_jsxs(Menu, { as: 'div', className: 'relative', children: [_jsx(MenuButton, { className: 'glass glass-border-subtle hover:bg-glass-interactive relative inline-flex h-9 w-9 items-center justify-center rounded-md border transition-colors', children: currentLanguage.flag }), _jsx(Transition, { as: Fragment, enter: 'transition ease-out duration-100', enterFrom: 'transform opacity-0 scale-95', enterTo: 'transform opacity-100 scale-100', leave: 'transition ease-in duration-75', leaveFrom: 'transform opacity-100 scale-100', leaveTo: 'transform opacity-0 scale-95', children: _jsx(MenuItems, { anchor: 'bottom end', className: 'glass-subtle glass-backdrop-sm glass-shadow-sm glass-border-strong z-100 w-48 rounded-md border ring-1 ring-black/5 [--anchor-gap:8px] focus:outline-none', children: _jsx("div", { className: 'py-1', children: availableLanguages.map((language) => (_jsx(MenuItem, { children: _jsxs("button", { onClick: () => changeLanguage(language.code), className: 'data-[focus]:bg-glass-interactive text-foreground group flex w-full items-center gap-2 px-3 py-2 text-sm transition-colors', children: [language.flag, _jsx("span", { className: 'whitespace-nowrap', children: language.name }), currentLocale === language.code && (_jsx("svg", { className: 'ml-auto h-4 w-4', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24', children: _jsx("path", { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M5 13l4 4L19 7' }) }))] }) }, language.code))) }) }) })] }));
57
57
  }
@@ -38,8 +38,8 @@ const ThemeSwitcher = () => {
38
38
  setTheme(nextTheme);
39
39
  };
40
40
  if (!mounted) {
41
- return (_jsx("button", { className: 'border-glass-strong bg-glass relative inline-flex h-9 w-9 items-center justify-center rounded-md border opacity-0', "aria-hidden": 'true', children: _jsx("div", { className: 'h-5 w-5' }) }));
41
+ return (_jsx("button", { className: 'glass glass-border-subtle relative inline-flex h-9 w-9 items-center justify-center rounded-md border opacity-0', "aria-hidden": 'true', children: _jsx("div", { className: 'h-5 w-5' }) }));
42
42
  }
43
- return (_jsxs("button", { onClick: toggleTheme, className: `border-glass-strong bg-glass hover:bg-glass-tinted relative inline-flex h-9 w-9 items-center justify-center rounded-md border ${canAnimateColors ? 'transition-colors' : ''}`, "aria-label": `Switch theme (current: ${currentThemeLabel})`, title: `Theme: ${currentThemeLabel} (click to change)`, children: [_jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', className: `absolute h-5 w-5 transition-all ${currentTheme === 'light' ? 'scale-100 rotate-0 opacity-100' : 'scale-0 rotate-90 opacity-0'}`, children: [_jsx("circle", { cx: '12', cy: '12', r: '5' }), _jsx("line", { x1: '12', y1: '1', x2: '12', y2: '3' }), _jsx("line", { x1: '12', y1: '21', x2: '12', y2: '23' }), _jsx("line", { x1: '4.22', y1: '4.22', x2: '5.64', y2: '5.64' }), _jsx("line", { x1: '18.36', y1: '18.36', x2: '19.78', y2: '19.78' }), _jsx("line", { x1: '1', y1: '12', x2: '3', y2: '12' }), _jsx("line", { x1: '21', y1: '12', x2: '23', y2: '12' }), _jsx("line", { x1: '4.22', y1: '19.78', x2: '5.64', y2: '18.36' }), _jsx("line", { x1: '18.36', y1: '5.64', x2: '19.78', y2: '4.22' })] }), _jsx("svg", { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', className: `absolute h-5 w-5 transition-all ${currentTheme === 'dark' ? 'scale-100 rotate-0 opacity-100' : 'scale-0 -rotate-90 opacity-0'}`, children: _jsx("path", { d: 'M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z' }) }), _jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', className: `absolute h-5 w-5 transition-all ${currentTheme === 'system' ? 'scale-100 rotate-0 opacity-100' : 'scale-0 rotate-90 opacity-0'}`, children: [_jsx("rect", { x: '3', y: '4', width: '18', height: '12', rx: '2' }), _jsx("line", { x1: '8', y1: '20', x2: '16', y2: '20' }), _jsx("line", { x1: '12', y1: '16', x2: '12', y2: '20' })] })] }));
43
+ return (_jsxs("button", { onClick: toggleTheme, className: `glass glass-border-subtle hover:bg-glass-interactive relative inline-flex h-9 w-9 items-center justify-center rounded-md border ${canAnimateColors ? 'transition-colors' : ''}`, "aria-label": `Switch theme (current: ${currentThemeLabel})`, title: `Theme: ${currentThemeLabel} (click to change)`, children: [_jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', className: `absolute h-5 w-5 transition-all ${currentTheme === 'light' ? 'scale-100 rotate-0 opacity-100' : 'scale-0 rotate-90 opacity-0'}`, children: [_jsx("circle", { cx: '12', cy: '12', r: '5' }), _jsx("line", { x1: '12', y1: '1', x2: '12', y2: '3' }), _jsx("line", { x1: '12', y1: '21', x2: '12', y2: '23' }), _jsx("line", { x1: '4.22', y1: '4.22', x2: '5.64', y2: '5.64' }), _jsx("line", { x1: '18.36', y1: '18.36', x2: '19.78', y2: '19.78' }), _jsx("line", { x1: '1', y1: '12', x2: '3', y2: '12' }), _jsx("line", { x1: '21', y1: '12', x2: '23', y2: '12' }), _jsx("line", { x1: '4.22', y1: '19.78', x2: '5.64', y2: '18.36' }), _jsx("line", { x1: '18.36', y1: '5.64', x2: '19.78', y2: '4.22' })] }), _jsx("svg", { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', className: `absolute h-5 w-5 transition-all ${currentTheme === 'dark' ? 'scale-100 rotate-0 opacity-100' : 'scale-0 -rotate-90 opacity-0'}`, children: _jsx("path", { d: 'M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z' }) }), _jsxs("svg", { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', className: `absolute h-5 w-5 transition-all ${currentTheme === 'system' ? 'scale-100 rotate-0 opacity-100' : 'scale-0 rotate-90 opacity-0'}`, children: [_jsx("rect", { x: '3', y: '4', width: '18', height: '12', rx: '2' }), _jsx("line", { x1: '8', y1: '20', x2: '16', y2: '20' }), _jsx("line", { x1: '12', y1: '16', x2: '12', y2: '20' })] })] }));
44
44
  };
45
45
  export default ThemeSwitcher;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/tooltip/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAoB,MAAM,OAAO,CAAC;AAcpD,UAAU,YAAY;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,SAAS,CAAC;CACpB;AAED,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,YAAY,+BA2DlE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/tooltip/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAY,MAAM,OAAO,CAAC;AAa5C,UAAU,YAAY;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,SAAS,CAAC;CACpB;AAED,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,YAAY,+BAsClE"}
@@ -1,40 +1,25 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useRef, useState } from 'react';
4
- import { FloatingPortal, arrow, autoUpdate, flip, offset, shift, useFloating, useHover, useInteractions, } from '@floating-ui/react';
3
+ import { useState } from 'react';
4
+ import { FloatingPortal, autoUpdate, flip, offset, shift, useFloating, useHover, useInteractions, } from '@floating-ui/react';
5
5
  export default function Tooltip({ children, content }) {
6
6
  const [isOpen, setIsOpen] = useState(false);
7
- const arrowRef = useRef(null);
8
- const { refs, floatingStyles, context, middlewareData, placement } = useFloating({
7
+ const { refs, floatingStyles, context } = useFloating({
9
8
  open: isOpen,
10
9
  onOpenChange: setIsOpen,
11
10
  placement: 'top',
12
11
  strategy: 'fixed',
13
12
  middleware: [
14
- offset(10),
13
+ offset(6),
15
14
  flip({
16
15
  fallbackAxisSideDirection: 'start',
17
16
  crossAxis: false,
18
17
  }),
19
18
  shift({ padding: 8 }),
20
- arrow({ element: arrowRef }),
21
19
  ],
22
20
  whileElementsMounted: autoUpdate,
23
21
  });
24
22
  const hover = useHover(context);
25
23
  const { getReferenceProps, getFloatingProps } = useInteractions([hover]);
26
- // Determine which side the arrow should be on based on placement
27
- const staticSide = {
28
- top: 'bottom',
29
- right: 'left',
30
- bottom: 'top',
31
- left: 'right',
32
- }[placement.split('-')[0]];
33
- return (_jsxs("div", { ref: refs.setReference, ...getReferenceProps(), className: 'inline-block', children: [children, isOpen && (_jsx(FloatingPortal, { children: _jsxs("div", { ref: refs.setFloating, style: floatingStyles, className: 'z-100 rounded bg-black px-2 py-1 text-sm text-white shadow-md dark:bg-white dark:text-black', ...getFloatingProps(), children: [content, _jsx("div", { ref: arrowRef, className: 'absolute h-2 w-2 rotate-45 bg-black dark:bg-white', style: {
34
- left: middlewareData.arrow?.x != null ? `${middlewareData.arrow.x}px` : '',
35
- top: middlewareData.arrow?.y != null ? `${middlewareData.arrow.y}px` : '',
36
- right: '',
37
- bottom: '',
38
- [staticSide]: '-4px',
39
- } })] }) }))] }));
24
+ return (_jsxs("div", { ref: refs.setReference, ...getReferenceProps(), className: 'inline-block', children: [children, isOpen && (_jsx(FloatingPortal, { children: _jsx("div", { ref: refs.setFloating, style: floatingStyles, className: 'glass-strong glass-texture-subtle glass-border-strong glass-backdrop-lg glass-shadow-lg text-foreground z-100 rounded-lg px-3 py-2 text-sm font-medium', ...getFloatingProps(), children: content }) }))] }));
40
25
  }
@@ -1,183 +1,326 @@
1
- # Glass 토큰/유틸 역할 정리 (modal 사용 기준 + globals.css 기반)
1
+ # Glass Token 기본 컴포넌트 가이드
2
2
 
3
- 문서는 **모달에서 실제 사용한 조합**과 `packages/core/src/styles/globals.css`의 정의를 확인한 뒤, glass 관련 토큰/유틸리티의 역할과 사용 기준을 정리한 것입니다.
3
+ 디렉터리의 `globals.css`는 패키지 소비 프로젝트에도 적용되는 glass UI 토큰의 원본입니다.
4
+ 컴포넌트 className을 작성할 때는 여기 정의된 glass token family를 조합하고, 소비 프로젝트에서 임의 glass 스타일을 새로 만들지 않습니다.
4
5
 
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`
6
+ ## 목표
10
7
 
11
- ## 2) globals.css 정의 기반 역할 요약
8
+ - 기본 컴포넌트가 light/dark 모드에서 같은 역할과 밀도를 갖게 만든다.
9
+ - 가장 자주 쓰는 기본 glass 표면은 짧게 `glass`로 쓴다.
10
+ - 표면이 없는 primitive는 `glass-transparent`로 명시한다.
11
+ - `rgba`, 임의 `backdrop-blur`, 임의 shadow, arbitrary `[box-shadow]` 대신 glass token class를 조합한다.
12
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)
13
+ ## Glass Token Family
27
14
 
28
- ### 2.2 유틸리티(클래스)
29
- globals.css 내 `@utility` 정의 기준
15
+ 실제 glass token class는 아래 family로만 작성한다.
30
16
 
31
- #### `glass`
32
- - **역할**: 기본 유리 표면. 그라데이션 + 그림자 + 내부 하이라이트 포함.
33
- - **대상**: 카드, 패널, 버튼, 인풋 등 “표면” 요소.
17
+ ```text
18
+ (glass-transparent | glass | glass-subtle | glass-surface | glass-strong | glass-selected | glass-disabled) [glass-texture*] [glass-border*] [glass-backdrop-*] [glass-shadow-*]
19
+ ```
34
20
 
35
- #### `glass-tinted`
36
- - **역할**: `glass`와 동일 효과 + 틴트된 표면(`--glass-surface-tinted`).
37
- - **대상**: 모달 패널, 강조 카드.
21
+ - `glass-transparent`: 표면 색 없이 유리 효과의 기본 구조와 내부 효과 슬롯만 만든다. semantic `bg-*`가 표면 역할을 하거나, 표면 없이 texture/border/backdrop/shadow만 조합할 때 쓰는 primitive entry다.
22
+ - `glass`: 패널과 카드에 쓰는 기본 glass surface entry다. 가장 흔한 표면이며 `glass-transparent`와 같이 붙이지 않는다.
23
+ - `glass-subtle`: 배경과 가깝게 붙는 낮은 밀도 표면이다. 반복 카드, 리스트 row, skeleton처럼 화면에 여러 번 나오는 요소에 쓴다.
24
+ - `glass-surface`: 기본보다 더 또렷한 foreground/control 표면이다. 버튼, toolbar, segmented control, 조작 가능한 입력 wrapper처럼 사용자가 직접 조작하는 작은 표면에 쓴다.
25
+ - `glass-strong`: popover, tooltip, menu, modal 안쪽의 떠 있는 레이어처럼 배경에서 강하게 분리되어야 하는 표면에 쓴다.
26
+ - `glass-selected`: selected tab, active segmented item, selected pill처럼 선택 상태 자체를 표현하는 표면이다.
27
+ - `glass-disabled`: disabled input, locked value처럼 비활성 또는 잠긴 값을 표현하는 표면이다.
28
+ - `glass-texture*`: 빛 하이라이트만 얹는다. 표면, 경계, blur를 만들지 않는다.
29
+ - `glass-border*`: 경계, 상단 하이라이트, 안쪽 윤곽선을 만든다. 기본값, `faint`, `subtle`, `medium`, `strong`이 있다.
30
+ - `glass-backdrop-*`: 뒤 배경 blur/saturate 단계만 정한다. `sm`, `md`, `lg`가 있다.
31
+ - `glass-shadow-*`: elevation만 정한다. `sm`, `md`, `lg`가 있다.
38
32
 
39
- #### `glass-glow`
40
- - **역할**: `glass`와 동일 효과 + 발광 표면(`--glass-surface-glow`).
41
- - **대상**: 강조 배지, 강조 카드.
33
+ `hover:`, `focus-visible:`, `disabled:`, `rounded-*`, `p-*`, `h-*`, `w-*`, `text-*`, `flex`, `grid`는 glass token이 아니라 Tailwind 보조 유틸리티다.
34
+ 토큰 조합을 정한 필요한 상태, 크기, 레이아웃 유틸리티를 붙인다.
42
35
 
43
- #### `glass-backdrop`
44
- - **역할**: 배경 블러 + 채도 증가.
45
- - **대상**: 모달 패널, 오버레이 같이 **뒤 배경이 비쳐야 하는 영역**.
36
+ `bg-glass-interactive`, `bg-glass-selected`는 `@theme`에 노출된 color utility다.
37
+ hover/focus/selected 배경을 맞출 쓰지만, surface entry family와 같은 것으로 설명하지 않는다.
46
38
 
47
- #### `glass-overlay`
48
- - **역할**: 기존 `bg-*` 색상을 유지한 채 유리 질감만 얹는다.
49
- - **대상**: 색상 버튼/배지/태그 같이 `bg-*`가 있는 요소.
39
+ ## Surface 선택 기준
50
40
 
51
- #### `border-glass-soft`
52
- - **역할**: 은은한 유리 테두리 + 내부 얕은 하이라이트.
53
- - **대상**: 보조 카드, 내부 블록.
41
+ - 넓은 section, form panel, summary panel:
42
+ `glass glass-border glass-backdrop-md glass-shadow-md`
43
+ - 반복 카드, 리스트 row, 낮은 우선순위 block:
44
+ `glass-subtle glass-border-subtle glass-backdrop-sm glass-shadow-sm`
45
+ - 버튼 묶음, toolbar, segmented control, 조작 가능한 입력 wrapper:
46
+ `glass-surface glass-border-medium glass-backdrop-md glass-shadow-md`
47
+ - 선택된 tab, segmented item, selected pill:
48
+ `glass-selected glass-texture-subtle glass-border-subtle glass-shadow-sm`
49
+ - 비활성 입력 또는 잠긴 값:
50
+ `glass-disabled glass-border-subtle glass-backdrop-sm glass-shadow-sm`
51
+ - 의미 색상 표면이 필요한 primary/destructive/semantic badge:
52
+ `glass-transparent` 위에 `bg-primary`, `bg-destructive/10`, `bg-emerald-*` 같은 semantic background를 붙인다.
54
53
 
55
- #### `border-glass-strong`
56
- - **역할**: 선명한 유리 테두리 + 내부 하이라이트.
57
- - **대상**: 모달 패널, 버튼, 입력처럼 **경계가 분명해야 하는 요소**.
54
+ `glass-surface`는 작은 control group에는 맞지만, page shell이나 넓은 카드에 반복하면 light mode에서 허옇게 뜰 수 있다.
55
+ 넓은 면적은 `glass` 또는 `glass-subtle`부터 시작한다.
58
56
 
59
- ## 3) 사용 규칙 (짧은 체크리스트)
60
- - 유리 표면에는 `border-glass-*`를 **필요할 때만** 붙여 경계를 잡는다.
61
- - 블러가 필요한 곳에만 `glass-backdrop`을 붙인다.
62
- - 강조 패널에는 `glass-tinted`, 일반 카드에는 `glass`가 기본.
63
- - 다크 모드에서는 텍스트/placeholder 대비를 추가로 보정한다 (`dark:*`).
64
- - **색상 버튼/배지**는 `bg-*` + `glass-overlay` 조합을 기본으로 사용한다.
57
+ ## 기본 className 조합
65
58
 
66
- ## 4) 적용 예시 (모달/입력/버튼)
59
+ ### Panel
67
60
 
68
- ### 4.1 모달 오버레이
69
61
  ```tsx
70
- <div className="glass fixed inset-0" />
62
+ <section className='glass glass-border glass-backdrop-md glass-shadow-md rounded-xl p-4 sm:p-5'>...</section>
71
63
  ```
72
- - **의도**: 배경을 얇게 덮고 유리감만 부여.
73
64
 
74
- ### 4.2 모달 패널
65
+ 영역, 검색 영역, 요약 영역의 기본 표면이다. 페이지 안에서 큰 면적을 차지하면 `glass-surface`를 기본값으로 넣지 않는다.
66
+
67
+ ### Subtle Card
68
+
75
69
  ```tsx
76
- <Dialog.Panel className="glass-tinted glass-backdrop border-glass-strong rounded-2xl border p-8 shadow-2xl">
77
- ...
78
- </Dialog.Panel>
70
+ <article className='glass-subtle glass-border-subtle glass-backdrop-sm glass-shadow-sm rounded-xl p-4'>...</article>
79
71
  ```
80
- - **의도**: 패널 자체는 틴트 유리 + 배경 블러 + 강한 경계.
81
72
 
82
- ### 4.3 모달 내부 컨테이너(본문 영역)
73
+ 반복 카드, 보조 정보, 리스트 row, skeleton의 기본 표면이다. 같은 화면에 여러 개 반복될 때는 이 조합을 우선한다.
74
+
75
+ ### List Row
76
+
83
77
  ```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
- ...
78
+ <div className='glass-subtle glass-border-subtle glass-backdrop-sm glass-shadow-sm flex items-center justify-between gap-3 rounded-lg p-3'>
79
+ <span className='text-sm font-medium'>Item</span>
80
+ <span className='text-muted-foreground text-xs'>Meta</span>
86
81
  </div>
87
82
  ```
88
- - **의도**: 내부 콘텐츠는 더 밝게, 다크 모드 대비 보정.
89
83
 
90
- ### 4.4 입력 필드
84
+ 리스트 안의 개별 행은 card보다 낮은 radius와 padding을 쓴다. 선택 여부는 row 전체 색을 과하게 올리기보다 badge나 selected surface를 보조로 쓴다.
85
+
86
+ ### Button
87
+
91
88
  ```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
- />
89
+ <button className='glass-surface glass-border-medium glass-backdrop-md glass-shadow-md text-foreground hover:bg-glass-interactive focus-visible:ring-ring/50 inline-flex h-10 items-center justify-center rounded-lg px-4 text-sm font-semibold transition-colors focus-visible:ring-2 focus-visible:outline-none'>
90
+ Button
91
+ </button>
92
+ ```
93
+
94
+ 일반 버튼은 control surface라서 `glass-surface`를 쓴다. 같은 화면에서 버튼이 많이 반복되면 `glass-subtle`로 낮춘다.
95
+
96
+ ### Primary Button
97
+
98
+ ```tsx
99
+ <button className='glass-transparent glass-texture-subtle glass-border-strong bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring/50 inline-flex h-10 items-center justify-center rounded-lg border px-4 text-sm font-semibold transition-colors focus-visible:ring-2 focus-visible:outline-none'>
100
+ Save
101
+ </button>
102
+ ```
103
+
104
+ primary는 `bg-primary`가 표면 역할을 한다. `glass` 또는 `glass-surface`를 추가하지 않고 `glass-transparent` primitive entry 위에 texture와 strong border만 얹는다.
105
+
106
+ ### Destructive Button
107
+
108
+ ```tsx
109
+ <button className='glass-transparent glass-texture glass-border-medium glass-backdrop-sm glass-shadow-sm bg-destructive/20 text-destructive hover:bg-destructive/30 focus-visible:ring-destructive/40 inline-flex h-10 items-center justify-center rounded-lg px-4 text-sm font-semibold transition-colors focus-visible:ring-2 focus-visible:outline-none'>
110
+ Delete
111
+ </button>
97
112
  ```
98
- - **의도**: 유리 표면 + 강한 경계 + 포커스 링 보정.
99
113
 
100
- ### 4.5 버튼
114
+ 삭제, 취소, 위험 동작은 의미 색상 배경과 텍스트를 유지한다. glass는 질감, 경계, elevation만 보강한다.
115
+
116
+ ### Disabled Button
117
+
101
118
  ```tsx
102
- <button className="glass border-glass-strong hover:bg-glass-tinted rounded-lg px-5 py-2 text-sm">
103
- ...
119
+ <button
120
+ disabled
121
+ className='glass-subtle glass-border-subtle glass-backdrop-sm glass-shadow-sm text-muted-foreground inline-flex h-10 items-center justify-center rounded-lg px-4 text-sm font-semibold opacity-60 disabled:cursor-not-allowed'>
122
+ Disabled
104
123
  </button>
105
124
  ```
106
- - **의도**: 기본은 glass, hover에서 tinted로 강조.
107
125
 
108
- ### 4.6 색상 버튼 (glass-overlay)
126
+ disabled 상태는 hover/focus 효과를 늘리지 않는다. 잠긴 값이나 비활성 입력은 `glass-disabled`를 쓴다.
127
+
128
+ ### Icon Button
129
+
109
130
  ```tsx
110
- <button className="glass-overlay bg-emerald-400 text-slate-900 rounded-lg px-5 py-2 text-sm">
111
- ...
131
+ <button
132
+ type='button'
133
+ aria-label='Edit'
134
+ className='glass-subtle glass-border-subtle glass-backdrop-sm glass-shadow-sm text-foreground hover:bg-glass-interactive focus-visible:ring-ring/50 inline-flex size-10 items-center justify-center rounded-lg transition-colors focus-visible:ring-2 focus-visible:outline-none'>
135
+ <Icon className='size-5' aria-hidden='true' />
112
136
  </button>
113
137
  ```
114
- - **의도**: `bg-*`를 유지하면서 glass 질감만 얹는다.
115
138
 
116
- ### 4.7 선택 상태 (glass-overlay)
139
+ 아이콘 버튼은 `aria-label`을 필수로 둔다. size는 `size-10`을 기본으로 해서 일반 `h-10` 버튼과 높이를 맞춘다.
140
+
141
+ ### Segmented Control
142
+
117
143
  ```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
- 콘텐츠 작성
144
+ <div
145
+ role='tablist'
146
+ aria-label='Density'
147
+ className='glass-surface glass-border-medium glass-backdrop-md glass-shadow-md inline-flex rounded-lg p-1'>
148
+ <button
149
+ type='button'
150
+ role='tab'
151
+ aria-selected='true'
152
+ className='glass-selected glass-texture-subtle glass-border-subtle glass-shadow-sm text-foreground rounded-md px-3 py-1.5 text-sm font-semibold'>
153
+ Compact
121
154
  </button>
122
- <button className="glass-overlay bg-slate-900 text-white rounded-full px-4 py-2 text-sm shadow-md">
123
- 렌더링 미리보기 (선택됨)
155
+ <button
156
+ type='button'
157
+ role='tab'
158
+ aria-selected='false'
159
+ className='text-muted-foreground hover:bg-glass-interactive focus-visible:ring-ring/50 rounded-md px-3 py-1.5 text-sm font-semibold transition-colors focus-visible:ring-2 focus-visible:outline-none'>
160
+ Comfort
124
161
  </button>
125
162
  </div>
126
163
  ```
127
- - **의도**: 탭 선택 상태에서 `bg-*`를 유지한 채 glass 질감을 추가한다.
128
164
 
129
- ### 4.8 기본 Solid 버튼 (non-glass)
165
+ group 표면은 `glass-surface`, 활성 항목은 `glass-selected`로 분리한다. 비활성 항목에는 glass를 중복 적용하지 않는다.
166
+
167
+ ### Input
168
+
130
169
  ```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>
170
+ <input
171
+ className='glass-subtle glass-border-subtle glass-backdrop-sm glass-shadow-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:bg-glass-selected focus-visible:ring-ring/50 h-10 w-full rounded-lg px-3 text-sm transition-colors outline-none focus-visible:ring-2'
172
+ placeholder='Search'
173
+ />
134
174
  ```
135
- - **의도**: 가장 기본적인 CTA 버튼. glass가 필요 없을 때 사용.
136
175
 
137
- ### 4.9 알림/에러 메시지 (glass + 컬러 배경)
176
+ 기본 입력은 `glass-subtle`로 낮춘다. 버튼과 나란히 놓이는 검색/필터 입력은 `h-10`, 테이블이나 인라인 편집 입력은 `h-9`를 쓴다.
177
+
178
+ ### Search Input
179
+
138
180
  ```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
- ...
181
+ <div className='glass-subtle glass-border-subtle glass-backdrop-sm glass-shadow-sm focus-within:bg-glass-selected focus-within:ring-ring/50 flex h-10 w-full items-center gap-2 rounded-lg px-3 transition-colors focus-within:ring-2'>
182
+ <Icon className='text-muted-foreground size-4 shrink-0' aria-hidden='true' />
183
+ <input className='placeholder:text-muted-foreground/70 min-w-0 flex-1 bg-transparent text-sm outline-none' />
141
184
  </div>
142
185
  ```
143
- - **의도**: 유리 표면 위에 컬러 배경을 얹어 메시지 톤을 유지한다.
144
186
 
145
- ## 5) 적용 순서 가이드
146
- 1. **표면 선택**: `glass` 또는 `glass-tinted`
147
- 2. **경계 부여**: `border-glass-strong` 또는 `border-glass-soft`
148
- 3. **배경 블러 필요 여부**: 필요하면 `glass-backdrop`
149
- 4. **다크 보정**: `dark:*`로 텍스트/placeholder 대비 조정
187
+ 아이콘이 들어간 검색 입력은 wrapper가 surface entry를 담당하고, 내부 input은 `bg-transparent`로 둔다.
188
+
189
+ ### Error Input
190
+
191
+ ```tsx
192
+ <input
193
+ aria-invalid='true'
194
+ aria-describedby='field-error'
195
+ className='glass text-foreground focus-visible:ring-destructive/30 border-destructive h-10 w-full rounded-lg border px-3 text-sm outline-none focus-visible:ring-2'
196
+ />
197
+ ```
198
+
199
+ 오류 입력은 glass border 단계보다 `border-destructive`와 오류 focus ring을 우선한다. 오류 메시지는 `aria-describedby`로 연결한다.
150
200
 
151
- ## 6) 추가 예시 (카드/리스트/배지)
201
+ ### Disabled Input
152
202
 
153
- ### 6.1 기본 카드
154
203
  ```tsx
155
- <div className="glass border-glass-soft rounded-2xl p-6 shadow-md">
156
- ...
204
+ <input
205
+ disabled
206
+ readOnly
207
+ className='glass-disabled glass-border-subtle glass-backdrop-sm glass-shadow-sm text-foreground/45 placeholder:text-foreground/35 dark:text-foreground/40 dark:placeholder:text-foreground/30 h-10 w-full cursor-not-allowed rounded-lg px-3 text-sm font-medium disabled:opacity-100'
208
+ />
209
+ ```
210
+
211
+ 비활성 입력은 opacity만 낮추지 말고 `glass-disabled`를 쓴다. 값이 잠긴 상태라면 `readOnly` 여부도 함께 명확히 한다.
212
+
213
+ ### Textarea
214
+
215
+ ```tsx
216
+ <textarea className='glass-subtle glass-border-subtle glass-backdrop-sm glass-shadow-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:bg-glass-selected focus-visible:ring-ring/50 min-h-28 w-full resize-y rounded-lg px-3 py-2.5 text-sm transition-colors outline-none focus-visible:ring-2' />
217
+ ```
218
+
219
+ textarea도 input과 같은 표면 규칙을 따른다. placeholder만 label처럼 쓰지 않고, 실제 label 또는 `aria-label`을 제공한다.
220
+
221
+ ### Checkbox Row
222
+
223
+ ```tsx
224
+ <label className='glass-subtle glass-border-subtle glass-backdrop-sm glass-shadow-sm flex items-center gap-3 rounded-lg p-3 text-sm'>
225
+ <input type='checkbox' className='text-primary size-4 rounded' />
226
+ <span>
227
+ <span className='block font-medium'>Compressed mode</span>
228
+ <span className='text-muted-foreground block text-xs'>Use in dense panels.</span>
229
+ </span>
230
+ </label>
231
+ ```
232
+
233
+ checkbox 자체는 표준 form control을 유지하고, 설명이 필요한 선택지만 row에 glass 표면을 준다.
234
+
235
+ ### Status Message
236
+
237
+ ```tsx
238
+ <div
239
+ role='status'
240
+ className='glass-surface glass-border-medium glass-backdrop-md glass-shadow-md rounded-lg p-4 text-sm'>
241
+ <p className='font-semibold'>Saved</p>
242
+ <p className='text-muted-foreground mt-1'>Changes were applied.</p>
157
243
  </div>
158
244
  ```
159
- - **의도**: 보조 영역에 은은한 유리감.
160
245
 
161
- ### 6.2 강조 카드 (CTA)
246
+ 일반 상태 메시지는 텍스트 의미를 우선한다. 성공, 안내, 저장 완료처럼 짧게 노출되는 상태에 쓴다.
247
+
248
+ ### Error Message
249
+
162
250
  ```tsx
163
- <div className="glass-tinted border-glass-strong rounded-2xl p-6 shadow-xl">
164
- ...
251
+ <div
252
+ role='alert'
253
+ className='glass-transparent glass-texture border-destructive/50 bg-destructive/10 text-destructive rounded-lg border p-4 text-sm'>
254
+ <p className='font-semibold'>Error</p>
255
+ <p className='mt-1'>Required token is missing.</p>
165
256
  </div>
166
257
  ```
167
- - **의도**: 시선이 가야 하는 블록에 강한 대비.
168
258
 
169
- ### 6.3 리스트 아이템
259
+ 오류 표면은 의미 색상 배경과 경계를 우선한다. `glass-surface`로 오류를 덮지 않는다.
260
+
261
+ ### Badge
262
+
170
263
  ```tsx
171
- <li className="glass border-glass-strong rounded-lg p-3 shadow">
172
- ...
173
- </li>
264
+ <span className='glass-transparent glass-texture-subtle glass-border-subtle glass-backdrop-sm bg-muted/45 text-muted-foreground inline-flex items-center rounded-full border px-3 py-1 text-xs font-semibold'>
265
+ Draft
266
+ </span>
174
267
  ```
175
- - **의도**: 반복 항목에 동일한 유리 표면 제공.
176
268
 
177
- ### 6.4 배지/태그
178
269
  ```tsx
179
- <span className="glass border-glass-strong rounded-full px-3 py-1 text-xs font-semibold">
180
- NEW
270
+ <span className='glass-transparent glass-texture glass-border-subtle glass-backdrop-sm inline-flex items-center rounded-full border bg-emerald-500/20 px-3 py-1 text-xs font-semibold text-emerald-950 dark:text-emerald-50'>
271
+ Active
181
272
  </span>
182
273
  ```
183
- - **의도**: 작은 요소에도 유리감 유지.
274
+
275
+ neutral badge는 muted 배경을 쓰고, semantic badge는 의미 색상과 light/dark 텍스트 대비를 함께 지정한다.
276
+
277
+ ### Skeleton
278
+
279
+ ```tsx
280
+ <div className='glass-subtle glass-border-subtle glass-backdrop-sm glass-shadow-sm w-full space-y-3 rounded-xl p-4 motion-safe:animate-pulse motion-reduce:animate-none'>
281
+ <div className='bg-muted/70 h-3 w-3/5 rounded-full' />
282
+ <div className='bg-muted/50 h-3 rounded-full' />
283
+ <div className='bg-muted/40 h-3 w-4/5 rounded-full' />
284
+ </div>
285
+ ```
286
+
287
+ skeleton은 낮은 표면과 muted block만 쓴다. motion은 `motion-reduce` 대응을 항상 같이 둔다.
288
+
289
+ ### Empty State
290
+
291
+ ```tsx
292
+ <div className='glass glass-border glass-backdrop-md glass-shadow-sm text-muted-foreground flex min-h-32 flex-col items-center justify-center rounded-xl p-4 text-center'>
293
+ <p className='text-foreground text-sm font-semibold'>No results</p>
294
+ <p className='mt-1 text-xs'>Adjust your filters.</p>
295
+ </div>
296
+ ```
297
+
298
+ empty state는 정보 밀도가 낮으므로 기본 surface와 낮은 shadow를 쓴다. 가능한 다음 행동을 짧은 문장으로 알려준다.
299
+
300
+ ## 작성 규칙
301
+
302
+ - 먼저 요소 역할에 맞는 glass entry를 하나만 고른다.
303
+ - 표면이 필요한 컴포넌트는 `glass`, `glass-subtle`, `glass-surface`, `glass-strong`, `glass-selected`, `glass-disabled` 중 하나를 entry로 쓴다.
304
+ - semantic 배경이 표면 역할을 하는 컴포넌트는 `glass-transparent`를 entry로 쓰고 surface entry를 붙이지 않는다.
305
+ - `glass-transparent`는 완전한 surface-less primitive다. background, backdrop-filter, border color, shadow를 직접 소유하지 않는다.
306
+ - 넓은 표면에는 `glass-surface`를 기본값으로 넣지 않는다.
307
+ - `glass-texture*`만으로 표면을 만들었다고 가정하지 않는다.
308
+ - semantic 상태는 `bg-primary`, `bg-destructive`, `bg-emerald-*`, `bg-amber-*`, `bg-sky-*`, `bg-rose-*` 같은 의미 색상을 유지한다.
309
+ - 모든 nested child를 glass card로 만들지 않는다. 역할이 없는 중첩은 spacing, divider, typography로 해결한다.
310
+
311
+ ## 금지 패턴
312
+
313
+ - `bg-background/45`, `ring-border/15`, 임의 `shadow-sm`, 임의 `backdrop-blur-*`, arbitrary `[box-shadow]`, inline `rgba()`로 glass를 새로 만들지 않는다.
314
+ - `glass-transparent glass`처럼 entry utility를 두 개 같이 붙이지 않는다.
315
+ - `glass glass-subtle`, `glass glass-surface`, `glass-subtle glass-surface`처럼 surface entry를 한 요소에 두 개 이상 붙이지 않는다.
316
+ - 기본 className 조합에서 `glass-strong`, `glass-border-strong`, `glass-backdrop-lg`, `glass-shadow-lg`를 일반 카드나 패널에 쓰지 않는다.
317
+ - 동일 화면에서 버튼, 입력, badge의 radius와 높이를 제각각 만들지 않는다.
318
+ - placeholder만 label처럼 쓰지 않는다.
319
+
320
+ ## Accessibility
321
+
322
+ - interactive element는 `focus-visible:ring-2 focus-visible:outline-none`와 적절한 `focus-visible:ring-*`를 유지한다.
323
+ - icon-only button은 `aria-label`을 제공하고, 장식 icon에는 `aria-hidden`을 둔다.
324
+ - input/textarea는 label, `aria-label`, `aria-describedby`, error message를 상황에 맞게 연결한다.
325
+ - 비활성 상태는 `disabled:cursor-not-allowed`, `disabled:opacity-*`, `glass-disabled` 중 역할에 맞는 조합을 쓴다.
326
+ - reduced transparency, high contrast, forced colors fallback은 `globals.css`가 담당한다. 컴포넌트에서 custom media query로 우회하지 않는다.