File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
08-14
08-14
08-18
08-18
08-14
08-19
08-19
08-14
08-14
08-18
08-18
08-14
08-18
08-18
08-19
08-19
// 말풍선 공용 조각.
//
// 꼬리가 달린 판(`FoxPopover` · `FoxTooltipRich`)이 같은 그림을 쓴다. 그 되풀이를 여기로 모은다.
// 믹스인만 있고 규칙이 없어 @use 해도 CSS가 늘지 않는다 — 부르는 자리에서만 나온다.
//
// ── 꼬리를 CSS로 그리는 이유 ───────────────────────────────────────────────────
//
// 시안은 꼬리를 22×12 SVG로 준다. 그 그림에는 평평한 끝을 덮는 1px 흰 띠가 들어 있는데,
// 꼬리를 판 위로 1px 겹쳐 올려 그 띠로 판의 1px 테두리를 지우는 구조다. Figma에서는 맞지만
// 브라우저에서는 **1px 두 개를 정확히 겹쳐야 하는 방식**이라, 소수 배율 화면(윈도우 125% 등)에서
// 둘이 서로 다른 물리 픽셀에 떨어지면 이음새에 실선이 비친다. 실제로 그렇게 보였다.
//
// 그래서 CSS로 그린다. **회전한 정사각형의 절반을 판 위에 걸치는** 방식이다:
//
// 1. 한 변이 22 ÷ √2(≈15.56px)인 정사각형을 45° 돌린다 → 밑변 22px, 높이 11px의 마름모
// 2. 그 마름모의 **가운데를 판과 맞닿는 변 위에** 둔다
// 3. 바깥을 향하는 두 변에만 1px 테두리를 준다 (안쪽 두 변은 테두리 없음)
//
// 그러면 안쪽 절반(11px)이 판의 테두리 선을 자기 배경으로 덮는다 — 1px끼리 맞추는 것이 아니라
// 11px짜리 면이 1px 선을 덮으므로 어떤 배율에서도 이음새가 남지 않는다. 바깥 두 변의 테두리는
// 마름모의 좌우 꼭짓점, 즉 판의 테두리와 같은 높이에서 끝나므로 선이 자연스럽게 이어진다.
//
// 45°로 돌리면 밑변과 높이의 비가 2:1이라 22×11이 된다. 시안은 22×12이므로 돌린 뒤 세로로
// 12/11만큼 늘인다(CSS 변환은 오른쪽부터 적용되므로 `scaleY(...) rotate(45deg)` 순서다).
// 빗변 테두리가 그만큼 두꺼워지지만 1.09배라 눈에 띄지 않는다.
//
// SVG를 쓰지 않으니 마스크 id가 문서에서 겹칠 일도 없다(시안 SVG를 그대로 넣으면 말풍선이
// 둘 이상 뜰 때 같은 id를 서로 참조한다).
//
// 토큰이 아닌 값:
// - ⚠️ 꼬리가 차지하는 자리 22×12px: 시안이 준 그림의 크기이고 토큰 스케일에 없다.
// - ⚠️ 정사각형의 한 변 `1.5556rem`: 밑변이 22px이 되는 값(22 ÷ √2)이다. 밑변을 바꾸려면
// 이 값을 √2로 나눠 다시 구한다.
// - ⚠️ `scaleY(12 / 11)`: 45° 마름모의 높이(11px)를 시안의 12px로 맞추는 보정이다.
// - 시안의 `gap: -1px`은 옮기지 않았다 — 정사각형의 안쪽 절반이 이미 판 위에 걸쳐 테두리를
// 덮으므로 따로 당길 필요가 없다.
@use "@fox/styles/abstracts" as fox;
/// 말풍선 바깥 틀. 꼬리와 판이 세로로 서고, 꼬리의 가로 위치를 이 정렬이 정한다.
@mixin root($inline-size: 36rem) {
box-sizing: border-box;
display: flex;
flex-direction: column;
inline-size: $inline-size;
}
/// 꼬리와 방향·위치 규칙 한 벌.
///
/// `$block`은 BEM 블록 이름(`"fox-popover"` 등)이고, `$border`·`$fill`은 판과 **같은 값**을
/// 넘긴다 — 꼬리는 판이 뾰족하게 튀어나온 부분이라 색이 갈리면 안 된다.
///
/// 부르는 쪽은 다음 마크업을 갖는다:
/// <div class="…__arrow-area"><span class="…__arrow"></span></div>
@mixin arrow($block, $border, $fill) {
$root: ".#{$block}";
// 좌우 여백이 꼬리를 판 가장자리에서 띄운다. **늘리지 않는다** — 폭이 내용(22px + 여백
// 48px)만큼이라야 루트의 `align-items`가 이 덩어리를 왼쪽·가운데·오른쪽으로 옮길 수 있다.
//
// 판보다 위에 칠해진다. 마름모의 안쪽 절반이 판의 테두리를 덮어야 하는데, `--top`에서는
// 꼬리가 DOM에서 먼저 와 판이 나중에 덧칠되기 때문이다.
#{$root}__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);
}
// 자리만 잡는다 — 그림은 아래 `::before`가 그린다(파일 상단 주석 참고).
#{$root}__arrow {
position: relative;
display: block;
inline-size: 2.2rem;
block-size: 1.2rem;
}
#{$root}__arrow::before {
content: "";
position: absolute;
inset-inline-start: 50%;
// 밑변이 22px이 되는 정사각형의 한 변: 22 ÷ √2.
inline-size: 1.5556rem;
block-size: 1.5556rem;
border-style: solid;
border-color: $border;
border-width: 0;
background: $fill;
}
// 정사각형의 가운데를 판과 맞닿는 변에 두고, 바깥을 향하는 두 변에만 테두리를 준다.
// 45° 돌리면 원래의 위·왼쪽 변이 위쪽 두 빗변이 되고, 아래·오른쪽 변이 아래쪽 두 빗변이 된다.
#{$root}--top #{$root}__arrow::before {
inset-block-start: 100%;
border-block-start-width: fox.border(1);
border-inline-start-width: fox.border(1);
transform: translate(-50%, -50%) scaleY(calc(12 / 11)) rotate(45deg);
}
#{$root}--bottom {
justify-content: flex-end;
}
#{$root}--bottom #{$root}__arrow::before {
inset-block-start: 0;
border-block-end-width: fox.border(1);
border-inline-end-width: fox.border(1);
transform: translate(-50%, -50%) scaleY(calc(12 / 11)) rotate(45deg);
}
// 꼬리의 가로 위치는 루트의 정렬이 정한다(시안 지정). 판은 `align-self: stretch`라 이 값에
// 영향받지 않고 언제나 폭을 꽉 채운다 — 움직이는 것은 꼬리뿐이다.
#{$root}--arrow-left {
align-items: flex-start;
}
#{$root}--arrow-center {
align-items: center;
}
#{$root}--arrow-right {
align-items: flex-end;
}
}