// FoxModal — 시안: 통합관리자페이지 디자인시스템(KE9UILWhB8qTBe14uXUn4F) // modal (190:1136 · 섹션 391:4640) // // 마크업 계약 (React 밖 소비자용): // // // 타이틀 // … // // // … // … // // …svg… // // ``는 `showModal()`로 열어야 뒷배경과 초점 가두기가 생긴다. // // 크기 넷(sm·md·lg·full)이 모두 폭 토큰이다. 모바일에서는 넷 다 같은 폭(348px)으로 // 모이고 여백·모서리·닫기 버튼도 함께 줄어든다 — 반응형 토큰이 그렇게 정의돼 있다. // // 제목 줄에만 오른쪽 여백을 준다 — 닫기 버튼이 그 자리에 겹쳐 앉기 때문이다. 본문과 // 아래쪽은 그 여백이 필요 없다. // // 닫기 버튼은 64px 칸의 아래쪽에 아이콘을 붙인다(시안 items-end). 그래야 아이콘이 제목 // 글줄과 눈높이가 맞는다. 상자 테두리 위에 얹히도록 1px 밖으로 내민다. // // 토큰이 아닌 값: // - 아래쪽 버튼의 최소 폭 8rem: 시안 값이나 Figma가 변수로 export하지 않는다(사용자 승인). // - 그림자 색: 시안이 테마 토큰이 아니라 `alpha/black10` 원시값에 직접 물려 놓았다 // (fox-carousel-button과 같은 사정). // - 뒷배경을 덮는 방식(`::backdrop`)과 가운데 정렬: 구조 규칙. // - line-height / letter-spacing: 시안 텍스트 스타일의 값이나 Figma가 변수로 // export하지 않는다. @use "abstracts" as fox; .fox-modal { box-sizing: border-box; // ``는 닫혀 있을 때 브라우저가 숨기는데, `display`를 주면 그 기본값을 덮어 // 닫힌 모달이 화면에 그대로 남는다. 열렸을 때만 배치를 준다. display: none; flex-direction: column; align-items: flex-start; // ``의 브라우저 기본 여백·테두리를 지우고 가운데에 놓는다. max-inline-size: calc(100% - #{fox.padding(7)} * 2); max-block-size: calc(100% - #{fox.padding(7)} * 2); margin: auto; padding: fox.modal(padding-default); // 세로만 스크롤한다. `overflow: auto`로 두면 닫기 버튼이 테두리를 덮으려고 일부러 1px // 밖으로 나간 것(`__close`의 `inset-inline-end: -1px`)까지 넘침으로 세어 가로 스크롤바가 // 생긴다 — 그 버튼은 배경도 테두리도 없고 아이콘이 8px 안쪽이라 x축을 잘라도 보이는 변화가 // 없다. overflow-x: hidden; overflow-y: auto; border: fox.border(1) solid fox.color(border-neutral-subtle); border-radius: fox.card(radius-xlg); background: fox.color(surface-neutral-default); color: fox.color(font-neutral-default); // 시안 neutral/lv3 — 그림자 둘이 겹친다. box-shadow: fox.shadow(location4) fox.shadow(location4) fox.shadow(blur4) 0 fox.primitive(alpha-black10), fox.shadow(location2) fox.shadow(location2) fox.shadow(blur3) 0 fox.primitive(alpha-black10); &::backdrop { background: fox.color(background-dim); } // 닫기 버튼이 상자 모서리에 얹히므로 기준점이 필요하다. position: relative; &[open] { display: flex; } // ── 크기 ────────────────────────────────────────────────────────────────── &--sm { inline-size: fox.modal(width-sm); } &--md { inline-size: fox.modal(width-md); } &--lg { inline-size: fox.modal(width-lg); } &--full { inline-size: fox.modal(width-full); } // ── 본문 ────────────────────────────────────────────────────────────────── &__body { display: flex; flex-direction: column; align-items: flex-start; gap: fox.gap(5); inline-size: 100%; } // 닫기 버튼이 오른쪽 위에 앉으므로 제목만 그만큼 비켜 준다. // // 제목이 없어도 한 줄 높이를 남긴다 — 시안의 tit 프레임이 제목 없이도 28px(제목 글자 // 크기 × 줄높이)을 차지한다. 이 자리를 없애면 닫기 버튼이 내용 위로 내려앉는다. &__tit { display: flex; flex-direction: column; align-items: flex-start; gap: fox.gap(3); inline-size: 100%; min-block-size: calc(#{fox.font-size(heading-md)} * 1.4); padding-inline-end: fox.padding(8); } &__title { inline-size: 100%; margin: 0; color: fox.color(font-neutral-strong); font-family: fox.font-family(title); font-size: fox.font-size(heading-md); font-weight: fox.font-weight(bold); line-height: 1.4; letter-spacing: -0.025em; word-break: break-word; } &__contents { display: flex; flex-direction: column; align-items: flex-start; gap: fox.gap(5); inline-size: 100%; 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; // 그림이 들어오면 폭을 채운다 — 시안의 contents 슬롯에 든 것도 폭 100%다. > img, > svg, > picture { inline-size: 100%; block-size: auto; } } // ── 아래쪽 ──────────────────────────────────────────────────────────────── &__foot { display: flex; align-items: center; justify-content: flex-end; gap: fox.gap(3); inline-size: 100%; padding-block-start: fox.padding(8); } // 왼편은 남는 폭을 먹어 버튼을 오른쪽 끝으로 민다. &__utility { display: flex; flex: 1 0 0; align-items: flex-start; gap: fox.gap(3); min-inline-size: 0; } &__actions { display: flex; flex-shrink: 0; align-items: center; justify-content: flex-end; gap: fox.gap(3); margin-inline-start: auto; } // 시안에서 모달 버튼은 최소 8rem이다 — 글자가 짧아도 두 버튼의 폭이 같아 보이게 한다. // 버튼 자체의 성질이 아니라 이 자리의 규칙이라 여기서 준다(btn 컴포넌트 세트에는 // 최소 폭 표기가 없다). &__actions .fox-button { min-inline-size: 8rem; } // ── 닫기 ────────────────────────────────────────────────────────────────── // 시안대로 아이콘을 칸 아래쪽에 붙여 제목 글줄과 눈높이를 맞춘다. &__close { position: absolute; inset-block-start: calc(-1 * #{fox.border(1)}); inset-inline-end: calc(-1 * #{fox.border(1)}); display: flex; align-items: flex-end; inline-size: fox.modal(btn-close); font-size: fox.modal(btn-close); block-size: fox.modal(btn-close); padding: fox.padding(3); border: none; background: none; color: fox.color(icon-neutral-default); cursor: pointer; } &__close-icon { display: inline-flex; > * { inline-size: fox.icon(5); font-size: fox.icon(5); block-size: fox.icon(5); } } }