diff --git a/src/frontend/src/components/ui/Button.tsx b/src/frontend/src/components/ui/Button.tsx index 65f0815..632a2f3 100644 --- a/src/frontend/src/components/ui/Button.tsx +++ b/src/frontend/src/components/ui/Button.tsx @@ -2,27 +2,41 @@ import type { ButtonHTMLAttributes, ReactNode } from 'react'; import './button.css'; type Variant = 'primary' | 'secondary' | 'ai' | 'ghost' | 'danger'; +type Size = 'sm' | 'md' | 'lg'; interface ButtonProps extends ButtonHTMLAttributes { variant?: Variant; + /** Nifty 3단 크기 — sm(32) · md(40, 기본) · lg(48). */ + size?: Size; leadingIcon?: ReactNode; + /** 정사각 아이콘 전용 버튼(aria-label 필수). */ + iconOnly?: boolean; block?: boolean; } -/** design.md §1-4 — 주요 버튼(primary blue), AI 액션(ai-accent 보라). radius 4px. */ +/** design.md §1-4 / DESIGN_SYSTEM_NIFTY §2.1 — 주요 버튼(primary blue), AI 액션(ai-accent 보라). radius 4px. */ export function Button({ variant = 'primary', + size = 'md', leadingIcon, + iconOnly, block, className = '', children, ...rest }: ButtonProps) { + const classes = [ + 'kx-btn', + `kx-btn--${variant}`, + size !== 'md' && `kx-btn--${size}`, + iconOnly && 'kx-btn--icon', + block && 'kx-btn--block', + className, + ] + .filter(Boolean) + .join(' '); return ( - diff --git a/src/frontend/src/components/ui/button.css b/src/frontend/src/components/ui/button.css index 76b44d5..1ea207e 100644 --- a/src/frontend/src/components/ui/button.css +++ b/src/frontend/src/components/ui/button.css @@ -20,11 +20,59 @@ opacity: 0.5; cursor: not-allowed; } +.kx-btn:focus-visible { + outline: 2px solid var(--focus-ring); + outline-offset: 2px; +} .kx-btn__icon { display: inline-flex; font-size: 18px; } +/* ── Nifty 3단 크기 (DESIGN_SYSTEM_NIFTY §2.1) — 기본 md(40) 불변 ── */ +.kx-btn--sm { + height: 32px; + padding: 0 var(--space-3); + font-size: var(--fs-caption); + gap: var(--space-1); +} +.kx-btn--lg { + height: 48px; + padding: 0 var(--space-5); + font-size: var(--fs-h3); +} + +/* ── 아이콘 전용(정사각) — aria-label 필수 ── */ +.kx-btn--icon { + width: 40px; + padding: 0; +} +.kx-btn--icon.kx-btn--sm { + width: 32px; +} +.kx-btn--icon.kx-btn--lg { + width: 48px; +} + +/* ── 버튼 그룹 — 인접 모서리 접합 ── */ +.kx-btn-group { + display: inline-flex; +} +.kx-btn-group .kx-btn { + border-radius: 0; +} +.kx-btn-group .kx-btn:first-child { + border-top-left-radius: var(--radius-sm); + border-bottom-left-radius: var(--radius-sm); +} +.kx-btn-group .kx-btn:last-child { + border-top-right-radius: var(--radius-sm); + border-bottom-right-radius: var(--radius-sm); +} +.kx-btn-group .kx-btn + .kx-btn { + margin-left: -1px; +} + .kx-btn--primary { background: var(--color-primary-600); color: var(--color-on-accent); diff --git a/src/frontend/src/screens/boothsales/boothsales.css b/src/frontend/src/screens/boothsales/boothsales.css index d19fd0d..1e4675e 100644 --- a/src/frontend/src/screens/boothsales/boothsales.css +++ b/src/frontend/src/screens/boothsales/boothsales.css @@ -45,7 +45,7 @@ border-radius: 999px; font-size: var(--fs-caption); background: var(--kx-surface-2, #f1f5f9); - border: 1px solid var(--kx-border, #e2e8f0); + border: 1px solid var(--color-neutral-200); } .kx-bsales__stat strong { font-size: 15px; font-weight: var(--fw-bold); } .kx-bsales__stat.is-available { color: #15803d; background: #f0fdf4; border-color: #bbf7d0; } @@ -81,7 +81,7 @@ padding: 14px; border-radius: 8px; background: var(--kx-surface, #fff); - border: 1px solid var(--kx-border, #e2e8f0); + border: 1px solid var(--color-neutral-200); border-left-width: 4px; } .kx-boothcard.is-available { border-left-color: #22c55e; } diff --git a/src/frontend/src/screens/cms/cms.css b/src/frontend/src/screens/cms/cms.css index 39b4326..381d0f1 100644 --- a/src/frontend/src/screens/cms/cms.css +++ b/src/frontend/src/screens/cms/cms.css @@ -1151,7 +1151,7 @@ margin-left: auto; font-size: var(--fs-micro); color: var(--color-neutral-500); - border: 1px dashed var(--color-neutral-300); + border: 1px dashed var(--color-neutral-200); border-radius: 6px; padding: 1px 6px; cursor: help; diff --git a/src/frontend/src/screens/design/studio.css b/src/frontend/src/screens/design/studio.css index 6243f64..44185c5 100644 --- a/src/frontend/src/screens/design/studio.css +++ b/src/frontend/src/screens/design/studio.css @@ -512,7 +512,7 @@ margin: var(--space-4) var(--space-5) 0; padding: var(--space-4); background: var(--color-surface, #fff); - border: 1px solid var(--color-border, #e5e8ef); + border: 1px solid var(--color-neutral-200); border-radius: var(--radius-lg); } .kx-studio__history-head { diff --git a/src/frontend/src/screens/floorplan/auto-layout.css b/src/frontend/src/screens/floorplan/auto-layout.css index 761b0f0..ba049f7 100644 --- a/src/frontend/src/screens/floorplan/auto-layout.css +++ b/src/frontend/src/screens/floorplan/auto-layout.css @@ -72,7 +72,7 @@ min-width: 0; padding: var(--space-2) var(--space-3); font-size: var(--fs-body); - border: var(--border-input, 1px solid #d0d5dd); + border: 1px solid var(--color-neutral-200); border-radius: var(--radius-md); color: var(--color-neutral-900); } diff --git a/src/frontend/src/screens/hallassign/hallassign.css b/src/frontend/src/screens/hallassign/hallassign.css index 264d7e9..d6a9a54 100644 --- a/src/frontend/src/screens/hallassign/hallassign.css +++ b/src/frontend/src/screens/hallassign/hallassign.css @@ -86,7 +86,7 @@ color: var(--color-neutral-700); } .kx-hall__chip.is-primary { - border-color: var(--color-brand-500, #1f29fc); + border-color: var(--color-primary-600); } .kx-hall__chip.is-conflict { background: #fef3f2; @@ -104,7 +104,7 @@ text-decoration: underline; } .kx-hall__chipbody:focus-visible { - outline: 2px solid var(--color-brand-500, #1f29fc); + outline: 2px solid var(--color-primary-600); outline-offset: 2px; } .kx-hall__chiptext { @@ -303,12 +303,12 @@ color: var(--color-neutral-700); } .kx-hall__statusbadge.is-primary { - border-color: var(--color-brand-500, #1f29fc); - color: var(--color-brand-500, #1f29fc); + border-color: var(--color-primary-600); + color: var(--color-primary-600); } .kx-hall__primarytag { font-size: var(--fs-caption); - color: var(--color-brand-500, #1f29fc); + color: var(--color-primary-600); } .kx-hall__detailsec { display: flex; diff --git a/src/frontend/src/screens/movein/movein.css b/src/frontend/src/screens/movein/movein.css index aedb1e9..c507cf2 100644 --- a/src/frontend/src/screens/movein/movein.css +++ b/src/frontend/src/screens/movein/movein.css @@ -413,7 +413,7 @@ gap: 10px; } .kx-logi__item { - border: 1px solid var(--color-border, #e4e7ec); + border: 1px solid var(--color-neutral-200); border-radius: var(--radius-md, 10px); padding: 12px 14px; background: var(--color-surface, #fff); @@ -444,7 +444,7 @@ gap: 8px; } .kx-logi__act { - border: 1px solid var(--color-border, #e4e7ec); + border: 1px solid var(--color-neutral-200); background: var(--color-surface, #fff); border-radius: var(--radius-sm, 8px); padding: 6px 12px; @@ -492,7 +492,7 @@ width: 10px; height: 10px; border-radius: 999px; - background: var(--color-border, #e4e7ec); + background: var(--color-neutral-200); } .kx-logi__track-dot.is-done { background: var(--color-primary, #1f29fc); diff --git a/src/frontend/src/screens/settlement/settlement.css b/src/frontend/src/screens/settlement/settlement.css index 5c558e5..91c563a 100644 --- a/src/frontend/src/screens/settlement/settlement.css +++ b/src/frontend/src/screens/settlement/settlement.css @@ -426,7 +426,7 @@ } .kx-settle__refund-row--em { padding-top: 4px; - border-top: 1px dashed var(--color-neutral-300, #d0d5dd); + border-top: 1px dashed var(--color-neutral-200); font-size: 13px; } .kx-settle__refund-row--em strong { diff --git a/src/frontend/src/screens/shared.css b/src/frontend/src/screens/shared.css index 1ab9303..f521eb7 100644 --- a/src/frontend/src/screens/shared.css +++ b/src/frontend/src/screens/shared.css @@ -141,15 +141,151 @@ box-shadow: 0 1px 2px rgba(16, 24, 40, 0.08); } -/* 셀렉트 */ +/* ══════════════════════════════════════════════════════════ + * 셀렉트 / 드롭다운 — Nifty(§2.4) 계보 + * 커스텀 캐럿(고정 중립 chevron, 양 테마 공용) + hover/focus/disabled 상태. + * appearance:none 로 OS별 기본 화살표 제거 후 kx 캐럿으로 통일. + * ══════════════════════════════════════════════════════════ */ .kx-select { height: 36px; - padding: 0 var(--space-3); + padding: 0 calc(var(--space-5) + 2px) 0 var(--space-3); + border: var(--border-card); + border-radius: var(--radius-sm); + background-color: var(--color-white); + color: var(--color-neutral-700); + font-size: var(--fs-body); + font-family: inherit; + line-height: 1; + appearance: none; + -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none' stroke='%23667085' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 4.5 6 7.5 9 4.5'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right var(--space-3) center; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} +.kx-select:not(:disabled):hover { + border-color: var(--color-neutral-500); +} +.kx-select:focus-visible { + outline: none; + border-color: var(--color-primary-600); + box-shadow: 0 0 0 3px var(--color-primary-050); +} +.kx-select:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: var(--color-neutral-050); +} +.kx-select--sm { + height: 32px; + font-size: var(--fs-caption); +} + +/* ── 드롭다운 메뉴 (§2.4) — 트리거 + 카드형 메뉴 + 항목. ── */ +.kx-menu { + min-width: 180px; + padding: var(--space-1); border: var(--border-card); border-radius: var(--radius-sm); background: var(--color-white); + box-shadow: var(--shadow-level2); +} +.kx-menu__item { + display: flex; + align-items: center; + gap: var(--space-2); + width: 100%; + padding: var(--space-2) var(--space-3); + border: none; + border-radius: var(--radius-sm); + background: transparent; font-size: var(--fs-body); color: var(--color-neutral-700); + text-align: left; + cursor: pointer; +} +.kx-menu__item:not(:disabled):hover, +.kx-menu__item.is-active { + background: var(--color-primary-050); + color: var(--color-primary-700); +} +.kx-menu__item--danger { + color: var(--color-error); +} +.kx-menu__item--danger:not(:disabled):hover { + background: var(--color-error-bg); + color: var(--color-error); +} +.kx-menu__item:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.kx-menu__divider { + height: 1px; + margin: var(--space-1) 0; + background: var(--color-neutral-200); +} + +/* ══════════════════════════════════════════════════════════ + * 리스트 그룹 (§2.5) — 공지·활동·검색결과 등 목록형. + * 행 = 아이콘/점 + 제목/메타 + 우측 배지/시간. 구분선·hover. + * ══════════════════════════════════════════════════════════ */ +.kx-list-group { + list-style: none; + margin: 0; + padding: 0; + border: var(--border-card); + border-radius: var(--radius-lg); + background: var(--color-white); + overflow: hidden; +} +.kx-list-group__item { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid var(--color-neutral-200); + font-size: var(--fs-body); + color: var(--color-neutral-700); +} +.kx-list-group__item:last-child { + border-bottom: none; +} +a.kx-list-group__item, +button.kx-list-group__item { + width: 100%; + border-left: none; + border-right: none; + border-top: none; + background: transparent; + text-align: left; + cursor: pointer; +} +a.kx-list-group__item:hover, +button.kx-list-group__item:hover, +.kx-list-group__item.is-active { + background: var(--color-primary-050); +} +.kx-list-group__item.is-active { + color: var(--color-primary-700); +} +.kx-list-group__body { + flex: 1; + min-width: 0; +} +.kx-list-group__title { + font-weight: var(--fw-semibold); + color: var(--color-neutral-900); +} +.kx-list-group__meta { + font-size: var(--fs-caption); + color: var(--color-neutral-500); +} +.kx-list-group__aside { + margin-left: auto; + flex: none; + font-size: var(--fs-caption); + color: var(--color-neutral-500); } /* KPI 카드(공통) — 카드 정본과 동일 Nifty 엘리베이션 */