diff --git a/packages/sw/ai/COMPONENT_CATALOG.md b/packages/sw/ai/COMPONENT_CATALOG.md index f9fd198..79c75cc 100644 --- a/packages/sw/ai/COMPONENT_CATALOG.md +++ b/packages/sw/ai/COMPONENT_CATALOG.md @@ -67,7 +67,7 @@ SW 공식 기준 Header(Figma 26:8944, 1920 × 49). 로고 · 파일 · 객체 | 컴포넌트 | 용도 | 주요 Props | 크기·동작 | 검증 | |---|---|---|---|---| -| `SWPopup` | 설정·정보·사용자 정의 Popup 프레임 | `title`(필수), `children`, `onClose`(닫기 버튼·Escape), `closeLabel`(기본 `닫기`), `titleActions`(제목 막대 닫기 버튼 앞 추가 버튼), `footer`(본문 뒤 고정 영역, 본문이 스크롤해도 보임), `autoFocus`(열릴 때 Focus, 안에서 닫히면 원래 위치로 복귀), `anchorRef`(기준 요소 아래 고정 Overlay), `style`·`className`·`div` 속성 | 폭 314, 제목줄 30, 높이는 콘텐츠(최대 화면 − 8px, `style.maxHeight`로 더 줄일 수 있음) → 본문 스크롤. Non-modal(Dim·Portal·Focus trap 없음) | ✅ | +| `SWPopup` | 설정·정보·사용자 정의 Popup 프레임 | `title`(필수), `children`, `onClose`(닫기 버튼·Escape), `closeLabel`(기본 `닫기`), `titleActions`(제목 막대 닫기 버튼 앞 추가 버튼), `footer`(본문 뒤 고정 영역, 본문이 스크롤해도 보임), `autoFocus`(열릴 때 Focus, 안에서 닫히면 원래 위치로 복귀. 기본: `anchorRef`가 있으면 true, 없으면 false), `anchorRef`(기준 요소 아래 고정 Overlay), `style`·`className`·`div` 속성 | 폭 314, 제목줄 30, 높이는 콘텐츠(최대 화면 − 8px, `style.maxHeight`로 더 줄일 수 있음) → 본문 스크롤. Non-modal(Dim·Portal·Focus trap 없음) | ✅ | | `SWPopupSection` | 속성창 그룹 면(제목 + 내용) | `title`, `children`, `section` 속성 | 그룹 제목 700·70% 검정, 내용 면 `--hm-sw-property-content` | ✅ | | `SWPopupRow` | 속성창 한 줄(라벨 + 값) | `label`(필수), `htmlFor`, `children`(값 컨트롤, SW 필드는 `SWHeaderInput`), `unit`, `field`(`box` 기본 · `compact` · `none`), `numeric`(숫자 편집 On), `readOnly`, `disabled` | 행 최소 32, 라벨 72, 값 상자 28(Default·Hover 테두리, 숫자 편집 시 #0F0D0A). `compact` = 41px hug 숫자 필드 | ✅ | | `SWHeaderPropertyPanel` | 데이터 기반 속성창(2절). 같은 프레임이며 `children`도 받음 | 2절 | 같은 DOM·class | ✅ | diff --git a/packages/sw/src/components/sw-header/components/SWHeaderPropertyPanel.tsx b/packages/sw/src/components/sw-header/components/SWHeaderPropertyPanel.tsx index 7e7c85a..a6c955b 100644 --- a/packages/sw/src/components/sw-header/components/SWHeaderPropertyPanel.tsx +++ b/packages/sw/src/components/sw-header/components/SWHeaderPropertyPanel.tsx @@ -327,13 +327,17 @@ export function SWPopupFrame({ if (!focus) return const panel = panelRef.current const previous = document.activeElement as HTMLElement | null - panel?.focus() return () => { // Closing with focus still inside (e.g. Escape on a field) returns it to where it came from. const active = document.activeElement if (panel && (active === document.body || active === null || panel.contains(active)) && previous?.isConnected) previous.focus() } }, [focus, panelRef]) + // An anchored frame stays hidden until it is placed, and a hidden element cannot take focus. + const placed = style.visibility !== "hidden" + useLayoutEffect(() => { + if (focus && placed) panelRef.current?.focus() + }, [focus, placed, panelRef]) return (