// FoxPopover — 시안: 통합관리자페이지 디자인시스템 Figma popover // // 꼬리가 달린 말풍선 판이다. 제목·본문·글자 버튼을 담고 오른쪽 위에 닫기가 붙는다. // // 마크업 계약 (React 밖 소비자용): //
//
// //
//
//
//
// 제목 //
//
// 본문 //
//
// // //
//
// // `--top`은 꼬리가 판 **위**에 붙고(트리거 아래에 뜨는 말풍선), `--bottom`은 아래에 붙는다. // 마크업 순서가 그것을 정한다 — `order`나 `column-reverse`를 쓰지 않는다. // // **자리를 스스로 잡지 않는다.** 트리거 옆 어디에 놓을지는 호출부가 `className`으로 준다. // 자리가 모자랄 때 위아래를 뒤집는 자동 배치도 없다 — 시안이 방향을 값으로 준다. // // ⚠️ 띄우는 방식이 `fox-select-option`과 같다. 감싼 요소 기준의 일반 흐름이라 // `overflow: hidden` 조상 안에서는 잘린다. HTML `popover` 속성으로 top layer에 올리는 방법이 // 있지만 위치를 잡아 주는 CSS Anchor Positioning이 아직 크롬 계열에만 있어 미룬다 — 옮기게 // 되면 셀렉트·칩·툴팁까지 한 번에 옮긴다. // // 꼬리는 **CSS가 그린다**(SVG를 쓰지 않는다). 그림과 방향·위치 규칙은 `_bubble.scss`의 // `arrow()` 믹스인이 갖는다 — `FoxTooltipRich`와 같은 그림이라 한곳에 둔다. 왜 시안 SVG를 // 쓰지 않는지도 그 파일 상단에 적혀 있다. // // 토큰이 아닌 값: // - ⚠️ `inline-size: 36rem`(360px): 시안 값 그대로다. 토큰 스케일에 360이 없다. 폭을 바꿔야 // 하는 자리는 호출부가 `className`으로 덮는다(`fox-snackbar`·`fox-alert`와 같은 처리). // - box-sizing / display / flex / position: 구조 규칙. // // 꼬리의 가로 위치는 **루트의 `align-items`**가 정한다(시안 지정). 꼬리 영역이 좌우 24px // 여백을 달고 다니므로, 왼쪽·오른쪽에서는 그만큼 안쪽에 서고 가운데에서는 여백이 대칭이라 // 정확히 가운데에 선다. @use "@fox/styles/abstracts" as fox; @use "@fox/styles/bubble" as bubble; .fox-popover { @include bubble.root; // 시안의 neutral/lv2 — 가까운 그림자와 먼 그림자 두 겹이다. // // ⚠️ `box-shadow`가 아니라 `drop-shadow`다. box-shadow는 요소의 **사각형 박스**를 따라 // 그려지므로, 꼬리 옆 빈 자리까지 포함한 네모 그림자가 나온다(시안은 Figma 프레임이 곧 // 모양이라 그 차이가 없다). drop-shadow는 **실제로 칠해진 모양**을 따라가므로 판과 꼬리가 // 하나의 말풍선으로 그림자를 진다. // // 겹쳐 쓰면 뒤의 그림자가 앞 그림자까지 다시 흐리게 하지만, 두 겹 다 10% 알파라 눈에 // 띄는 차이는 없다. filter: drop-shadow( #{fox.shadow(location1)} #{fox.shadow(location1)} #{fox.shadow(blur2)} #{fox.primitive(alpha-black10)} ) drop-shadow( #{fox.shadow(location3)} #{fox.shadow(location3)} #{fox.shadow(blur3)} #{fox.primitive(alpha-black10)} ); // ── 판 ────────────────────────────────────────────────────────────────────── &__content { // 닫기 버튼의 위치 기준이다. position: relative; box-sizing: border-box; display: flex; flex-direction: column; align-items: flex-start; gap: fox.gap(4); align-self: stretch; padding: fox.padding(8); border: fox.border(1) solid fox.color(border-neutral-strong); border-radius: fox.radius(5); background: fox.color(surface-neutral-default); } // ── 꼬리 ──────────────────────────────────────────────────────────────────── // 좌우 여백이 꼬리를 판 가장자리에서 띄운다. **늘리지 않는다** — 폭이 내용(22px + 여백 // 48px)만큼이라야 루트의 `align-items`가 이 덩어리를 왼쪽·가운데·오른쪽으로 옮길 수 있다. // // 판보다 **위에 칠해진다**(시안 구조). 꼬리가 판 쪽으로 1px 파고들고, 꼬리 안의 흰 띠가 그 // 자리의 판 테두리를 지워 말풍선 안팎이 하나로 이어진다. 쌓임 순서를 올리지 않으면 // `--top`에서 판이 나중에 칠해져 그 선이 되살아난다. &__arrow-area { position: relative; z-index: 1; box-sizing: border-box; display: flex; flex-direction: column; align-items: flex-start; padding-inline: fox.padding(8); } // ── 글자 ──────────────────────────────────────────────────────────────────── &__body { display: flex; flex-direction: column; align-items: flex-start; align-self: stretch; } // 오른쪽 여백은 닫기 버튼 자리다 — 제목이 그 밑으로 들어가지 않게 비워 둔다. &__title-row { display: flex; align-items: center; gap: fox.gap(5); align-self: stretch; padding-inline-end: fox.padding(9); } &__title { flex: 1 0 0; min-inline-size: 0; color: fox.color(font-neutral-strong); font-family: fox.font-family(title); font-size: fox.font-size(heading-xsm); font-weight: fox.font-weight(bold); line-height: 1.4; letter-spacing: -0.025em; } // 제목과 본문 사이 간격을 본문의 윗여백이 맡는다(시안이 그렇게 준다) — 제목만 있을 때 // 아래로 빈자리가 남지 않는다. &__message-row { display: flex; align-items: flex-start; align-self: stretch; padding-block-start: fox.padding(6); } &__message { flex: 1 0 0; min-inline-size: 0; color: fox.color(font-neutral-default); font-family: fox.font-family(body); font-size: fox.font-size(body-sm); font-weight: fox.font-weight(regular); line-height: 1.5; letter-spacing: -0.025em; } // 글자 버튼은 자기 모양을 그대로 갖는다 — 여기서는 줄어들지만 않게 막는다. &__action { flex-shrink: 0; } // ── 닫기 ──────────────────────────────────────────────────────────────────── // 판 오른쪽 위 모서리에 붙는다. 누르는 자리는 40px 정사각이고 아이콘은 16px이다. // // ⚠️ 시안이 `align-items: flex-end`를 준다 — 가로는 시작(왼쪽), 세로는 끝(아래)이라 // 아이콘이 40px 칸의 왼쪽 아래로 치우친다(`fox-snackbar`의 닫기와 같다). &__close { position: absolute; inset-block-start: 0; inset-inline-end: 0; box-sizing: border-box; display: flex; align-items: flex-end; inline-size: fox.number(14); block-size: fox.number(14); aspect-ratio: 1 / 1; padding: fox.padding(2); border: none; background: none; color: fox.color(icon-neutral-default); cursor: pointer; } &__close > * { flex-shrink: 0; inline-size: fox.icon(3); block-size: fox.icon(3); aspect-ratio: 1 / 1; } } // 꼬리. 판과 같은 색을 넘긴다 — 꼬리는 판이 뾰족하게 튀어나온 부분이다. @include bubble.arrow( "fox-popover", $border: fox.color(border-neutral-strong), $fill: fox.color(surface-neutral-default) );