File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
08-14
08-14
08-14
08-18
08-19
08-19
// FoxSnackbar — 시안: 통합관리자페이지 디자인시스템 Figma snackbar
//
// 흰 판 위에 글자(제목·본문)와 글자 버튼이 서고, 오른쪽 위에 닫기가 붙는다. 제목과 본문은
// 각각 없을 수 있다 — 한쪽만 있으면 그 줄만 그린다. `FoxToast`와 형제지만
// **누를 것이 있다** — 그래서 스스로 사라지지 않고 손이 닿을 때까지 남는다.
//
// 마크업 계약 (React 밖 소비자용):
// <div class="fox-snackbar" role="status">
// <span class="fox-snackbar__icon">…아이콘 svg…</span> <!-- 선택 -->
// <div class="fox-snackbar__content">
// <strong class="fox-snackbar__title">저장했습니다</strong> <!-- 선택 -->
// <span class="fox-snackbar__message">3분 안에 되돌릴 수 있습니다.</span> <!-- 선택 -->
// </div>
// <button class="fox-text-button fox-text-button--primary fox-text-button--md
// fox-snackbar__action">실행 취소</button> <!-- 선택 -->
// <button class="fox-snackbar__close" aria-label="닫기">…X svg…</button> <!-- 선택 -->
// </div>
//
// 글자 버튼은 `FoxTextButton`의 primary·md를 그대로 쓴다 — 스낵바가 버튼 모양을 다시 정하지
// 않고 `fox-snackbar__action`은 배치만 맡는다.
//
// 토큰이 아닌 값:
// - ⚠️ `inline-size` / `min-inline-size: 64rem`: **시안의 폭(640px)을 그대로 적용해 둔
// 것이다.** Figma가 변수로 export하지 않았고 토큰 스케일에도 640이 없다. 폭을 바꿔야 하는
// 자리는 호출부가 `className`으로 덮는다(`fox-toggle-quantity`·`fox-alert`와 같은 처리).
// - ⚠️ `padding-inline-end`가 64px이라 토큰이 없다. 닫기가 `right: 0`으로 오른쪽 끝에
// 붙으므로(시안 지정) 이 여백은 **글자·버튼이 닫기 밑으로 들어가지 않게 비워 두는
// 자리**다. 20px + 아이콘 24px + 20px = 64px로 떨어져서 그 조합으로 적는다
// (`padding(7)` + `icon(5)` + `padding(7)`).
// - line-height / letter-spacing: 시안 텍스트 스타일 heading/xsm의 값이나 Figma가 변수로
// export하지 않는다. -0.375px은 15px 기준이라 `-0.025em`으로 적는다(다른 컴포넌트와 같은
// 근거).
// - box-sizing / position / display / flex: 구조 규칙.
//
// ⚠️ 그림자 색: 시안이 두 겹을 각각 `dark/alpha/black10`·`alpha/black10`으로 물려 놓았는데
// 값이 같고(rgba(0,0,0,0.10)) 앞쪽 이름의 토큰은 export에 없다. 두 겹 모두 `alpha-black10`을
// 쓴다 — `fox-toast`·`fox-select-option`과 같은 선택이다.
//
// ⚠️ 폭 세 값(640 / 640 / `grid(wrap-sm)`)은 **PC 기준이다.** 모바일에서 `grid(wrap-sm)`은
// 320px인데 최소 폭 640px이 그것을 이겨 화면 밖으로 넘친다 — 실제로 잘렸다. 그래서 모바일에서는
// 최소 폭을 풀고 판이 부모 폭을 채우게 둔다(아래 `fox.mobile` 블록).
//
// 시안이 모바일 스낵바의 폭을 따로 주면 그 값으로 바꾼다. 지금은 "화면을 넘지 않는다"만
// 보장한다.
@use "@fox/styles/abstracts" as fox;
.fox-snackbar {
// 안쪽 여백이 바깥 폭을 늘리지 않게 한다 — 640px이 640px로 그려진다.
box-sizing: border-box;
// 닫기 버튼의 위치 기준이 된다.
position: relative;
display: flex;
align-items: center;
gap: fox.gap(5);
inline-size: 64rem;
min-inline-size: 64rem;
max-inline-size: fox.grid(wrap-sm);
padding-block: fox.padding(6);
padding-inline-start: fox.padding(7);
// 닫기 버튼이 앉을 자리(위 주석 참고).
padding-inline-end: calc(#{fox.padding(7)} + #{fox.icon(5)} + #{fox.padding(7)});
border: fox.border(1) solid fox.color(border-neutral-subtle);
border-radius: fox.radius(5);
background: fox.color(surface-neutral-default);
// 시안의 neutral/lv2 — 가까운 그림자와 먼 그림자 두 겹이다.
box-shadow:
fox.shadow(location1) fox.shadow(location1) fox.shadow(blur2) 0
fox.primitive(alpha-black10),
fox.shadow(location3) fox.shadow(location3) fox.shadow(blur3) 0
fox.primitive(alpha-black10);
// 모바일에서는 640px 고정을 풀고 부모 폭을 채운다(위 주석 참고). `min-inline-size`를 함께
// 풀지 않으면 640px이 그대로 남아 화면 밖으로 밀려 잘린다.
@include fox.mobile {
inline-size: 100%;
min-inline-size: 0;
max-inline-size: 100%;
}
// 글자 앞 아이콘. 글자가 길어 판이 높아져도 크기가 그대로여야 해서 줄어들지 않게 막는다.
//
// 색은 `icon-theme-primary`다(시안 확인). 글리프가 `fill: currentColor`라 슬롯에 색만
// 주면 그대로 입는다 — 굵기는 호출부가 넘기는 글리프가 정하고 시안은 `fill`이다.
//
// 크기는 시안 값 그대로 24px(`icon(5)`)이고 닫기 아이콘과 같다. 세로 정렬은 시안에 없어
// 판 규칙(가운데)을 그대로 따른다.
&__icon {
display: inline-flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
inline-size: fox.icon(5);
font-size: fox.icon(5);
block-size: fox.icon(5);
aspect-ratio: 1 / 1;
color: fox.color(icon-theme-primary);
}
// 넘긴 SVG가 슬롯을 꽉 채운다 — 크기는 슬롯이 정하고 SVG는 따라온다.
&__icon > * {
inline-size: 100%;
block-size: 100%;
}
// 남는 폭을 전부 가져가 글자 버튼을 오른쪽 끝으로 민다. `min-inline-size: 0`이 없으면
// 긴 글이 줄바꿈되지 않고 판을 밀어 넓힌다(flex 자식의 기본 최소 크기가 내용 크기라서다).
//
// `flex: 1 0 0`으로 남는 폭을 전부 가져가 글자 버튼을 오른쪽 끝으로 민다. 제목과 본문
// 사이는 `gap(1)`(2px)이고 둘 다 왼쪽에 붙는다 — 시안 확인 완료(`FoxAlert`의 같은 자리와
// 같은 값이다).
&__content {
display: flex;
flex: 1 0 0;
flex-direction: column;
align-items: flex-start;
gap: fox.gap(1);
min-inline-size: 0;
}
&__title {
align-self: stretch;
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 {
align-self: stretch;
color: fox.color(font-neutral-strong);
font-family: fox.font-family(body);
font-size: fox.font-size(body-md);
font-weight: fox.font-weight(regular);
line-height: 1.5;
letter-spacing: -0.025em;
}
// 글자 버튼은 자기 모양을 그대로 갖는다 — 여기서는 줄어들지만 않게 막는다.
&__action {
flex-shrink: 0;
}
// 오른쪽 끝에 붙는다(시안 지정 `right: 0`). 흐름에서 빠져 있어 글자·버튼 배치에 끼어들지
// 않고, 판의 오른쪽 여백(64px)이 그 아래로 글자가 들어가지 않게 막아 준다.
//
// 누르는 자리는 48px 정사각이고 아이콘은 24px이다 — 손가락이 닿는 넓이를 아이콘보다 크게
// 잡은 것이라, 여백을 줄이면 모바일에서 누르기 어려워진다.
//
// ⚠️ 시안이 이 칸에 `align-items: flex-end`를 준다. 가로로는 시작(왼쪽), 세로로는 끝(아래)에
// 붙어서 **아이콘이 48px 칸의 왼쪽 아래로 치우친다** — 오른쪽 끝에서 16px, 아래에서 8px이다.
// 시안 값을 그대로 옮긴 결과이니, 화면에서 가운데로 보여야 하면 `center`로 바꾼다.
//
// 세로는 판 위쪽에 붙는다(시안 지정 `top: 0`). 가운데 정렬이 아니라서, 본문이 늘어 판이
// 높아져도 닫기는 첫 줄 옆에 그대로 남는다.
&__close {
position: absolute;
inset-inline-end: 0;
inset-block-start: 0;
display: flex;
align-items: flex-end;
inline-size: fox.number(16);
font-size: fox.number(16);
block-size: fox.number(16);
aspect-ratio: 1 / 1;
padding: fox.padding(3);
border: none;
background: none;
color: fox.color(icon-neutral-default);
cursor: pointer;
}
// 아이콘은 24px 정사각으로 못박는다 — 칸이 48px이라 늘어나게 두면 안 된다.
&__close > * {
flex-shrink: 0;
inline-size: fox.icon(5);
font-size: fox.icon(5);
block-size: fox.icon(5);
aspect-ratio: 1 / 1;
}
}