File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
// 개발 전용 테스트 페이지 스타일.
//
// 이 파일 자체가 토큰 규율의 첫 소비처다 — 여기 있는 모든 값은 `fox.*()` 함수를 거치므로,
// 없는 토큰을 쓰면 빌드가 실패한다. 페이지가 그려진다는 것은 사용된 토큰이 전부 실재한다는
// 뜻이기도 하다.
@use "@fox/styles/abstracts" as fox;
.shell {
display: grid;
grid-template-columns: 1fr;
min-block-size: 100dvh;
background: fox.color(background-default);
color: fox.color(font-neutral-default);
@include fox.pc {
// 화면을 넘지 않게 못박고 스크롤은 두 칸이 각자 갖는다.
block-size: 100dvh;
grid-template-columns: 24rem 1fr;
overflow: hidden;
}
}
.loading {
padding: fox.padding(8);
color: fox.color(font-neutral-subtle);
font-size: fox.font-size(body-md);
}
// ── 사이드바 ────────────────────────────────────────────────────────────────
// 본 프로젝트 사이드바(app/(protected)/(basic)/_components/admin-sidebar)와 같은 생김새다.
// @fox 폴더만 복사해도 하니스가 따라가야 해서 규칙을 옮겨 적었다.
.sidebar {
box-sizing: border-box;
display: flex;
flex-direction: column;
padding: fox.padding(5);
border-block-end: fox.border(1) solid fox.color(border-neutral-subtler);
background: linear-gradient(
to bottom,
fox.color(surface-neutral-gray) 70%,
fox.color(surface-theme-primary)
);
@include fox.pc {
min-block-size: 0;
overflow-y: auto;
border-block-end: 0;
box-shadow: inset calc(-1 * #{fox.border(1)}) 0 0 0
fox.color(border-neutral-subtler);
}
}
.top {
display: flex;
flex-direction: column;
gap: fox.gap(3);
padding-block-end: fox.spacing(bottom-sm);
border-block-end: fox.border(1) solid fox.color(border-neutral-subtle);
}
// FoxInput의 기본 테두리는 input-border(진한 회색)인데 시안 검색창은 border-neutral-subtler다.
.search :global(.fox-input__box) {
border-color: fox.color(border-neutral-subtler);
}
// 세그먼티드 컨트롤은 inline-flex라 내용만큼만 넓다 — 늘리지 않고 줄 가운데에 세운다.
.themeRow {
display: flex;
justify-content: center;
}
.empty {
padding-block: fox.spacing(top-md);
color: fox.color(font-neutral-subtler);
font-size: fox.font-size(label-sm);
}
// ── 섹션 ────────────────────────────────────────────────────────────────────
.section {
display: flex;
inline-size: 100%;
flex-direction: column;
align-items: flex-start;
padding-block-start: fox.spacing(top-sm);
padding-block-end: fox.spacing(bottom-md);
}
.sectionTitle {
inline-size: 100%;
margin: 0;
padding-block-end: fox.spacing(bottom-sm);
color: fox.color(font-neutral-subtler);
font-family: fox.font-family(title);
font-size: fox.font-size(heading-2xsm);
font-weight: fox.font-weight(bold);
line-height: 1.4;
letter-spacing: -0.025em;
}
.list {
display: flex;
inline-size: 100%;
flex-direction: column;
align-items: flex-start;
margin: 0;
padding: 0;
list-style: none;
}
.item {
inline-size: 100%;
}
// ── 1depth ──────────────────────────────────────────────────────────────────
.itemButton {
display: flex;
inline-size: 100%;
align-items: center;
gap: fox.gap(2);
padding-block: fox.padding(4);
padding-inline: fox.padding(3);
border: none;
border-radius: fox.radius(3);
background: fox.color(action-secondary);
color: fox.color(font-neutral-subtle);
font-family: fox.font-family(title);
font-size: 1.4rem;
font-weight: fox.font-weight(bold);
line-height: 1.4;
letter-spacing: -0.025em;
text-align: start;
cursor: pointer;
mix-blend-mode: multiply;
}
.itemButtonActive {
background: fox.color(action-secondary-active);
color: fox.color(font-neutral-strong-inverse);
mix-blend-mode: normal;
}
.itemLabel {
flex: 1 0 0;
min-inline-size: 0;
}
// ── 2depth ──────────────────────────────────────────────────────────────────
.childGroup {
display: flex;
inline-size: 100%;
flex-direction: column;
align-items: flex-start;
margin: fox.gap(2) 0 0;
padding: fox.padding(3);
border-radius: fox.radius(3);
box-shadow: inset 0 0 0 fox.border(1) fox.color(border-neutral-subtler);
background: fox.color(surface-neutral-default);
list-style: none;
}
.child {
inline-size: 100%;
}
.childRow {
display: flex;
inline-size: 100%;
align-items: center;
gap: fox.gap(2);
padding-block: fox.padding(2);
padding-inline: fox.padding(3);
border: none;
border-radius: fox.radius(3);
background: fox.color(action-secondary);
color: fox.color(font-neutral-default);
font-family: fox.font-family(title);
font-size: 1.4rem;
font-weight: fox.font-weight(bold);
line-height: 1.4;
letter-spacing: -0.025em;
text-align: start;
cursor: pointer;
}
.childRowActive {
color: fox.color(font-theme-secondary);
}
// 별·캐럿 자리는 항상 잡아 두고 필요 없을 때 투명하게만 만든다 — 라벨 시작 위치가
// 항목마다 흔들리지 않게 하기 위해서다.
.childMark {
display: inline-flex;
flex-shrink: 0;
inline-size: fox.icon(2);
block-size: fox.icon(2);
}
.childMark > svg {
inline-size: 100%;
block-size: 100%;
}
.childMarkHidden {
opacity: 0;
}
.childLabel {
flex: 1 0 0;
min-inline-size: 0;
}
// ── 3depth ──────────────────────────────────────────────────────────────────
.leafGroup {
display: flex;
inline-size: 100%;
flex-direction: column;
align-items: flex-start;
margin: 0;
padding-block: fox.padding(2);
padding-inline-start: fox.padding(7);
padding-inline-end: fox.padding(2);
border-radius: fox.radius(3);
list-style: none;
}
.leaf {
display: flex;
inline-size: 100%;
align-items: center;
gap: fox.gap(2);
padding-block: fox.padding(1);
padding-inline: fox.padding(2);
border: none;
background: none;
color: fox.color(font-neutral-subtle);
font-family: fox.font-family(body);
font-size: 1.3rem;
line-height: 1.5;
letter-spacing: -0.025em;
text-align: start;
cursor: pointer;
}
.leafActive {
color: fox.color(font-theme-secondary);
}
// 시안에 없는 상태 — 키보드로 훑을 수 있어야 해서 남긴다.
.itemButton:focus-visible,
.childRow:focus-visible,
.leaf:focus-visible {
outline: fox.border(2) solid fox.color(border-theme-primary);
outline-offset: fox.border(1);
}
// 시안에 없는 컴포넌트별 개수 표시. 캐럿이 서는 자리에 둔다.
.count {
flex-shrink: 0;
}
// "추가 방법"은 컴포넌트가 아니라 안내다. 목록 맨 아래에 두고 테두리로 구분해, 등록된
// 컴포넌트와 같은 층위로 읽히지 않게 한다.
.guideButton {
margin-block-start: fox.gap(2);
box-shadow: inset 0 0 0 fox.border(1) fox.color(border-neutral-strong);
background: none;
color: fox.color(font-neutral-subtler);
mix-blend-mode: normal;
}
.guideButtonActive {
box-shadow: inset 0 0 0 fox.border(1) fox.color(border-theme-primary);
color: fox.color(font-theme-primary);
}
.guideMark {
font-size: fox.font-size(label-lg);
line-height: 1;
}
// ── 본문 ────────────────────────────────────────────────────────────────────
.content {
min-inline-size: 0;
padding: fox.spacing(top-md) fox.padding(7) fox.spacing(bottom-xlg);
@include fox.pc {
min-block-size: 0;
overflow-y: auto;
}
@include fox.pc {
padding: fox.spacing(top-lg) fox.padding(11) fox.spacing(bottom-xlg);
}
}
.section {
display: flex;
flex-direction: column;
}
.sectionTitle {
font-size: fox.font-size(heading-md);
font-weight: fox.font-weight(bold);
}
.sectionNote {
margin-block: fox.gap(2) fox.gap(6);
color: fox.color(font-neutral-subtle);
font-size: fox.font-size(body-sm);
}
.code {
overflow-x: auto;
margin-block-start: fox.gap(4);
padding: fox.padding(6);
border: fox.border(1) solid fox.color(border-neutral-subtle);
border-radius: fox.radius(3);
background: fox.color(surface-neutral-gray);
color: fox.color(font-neutral-default);
font-size: fox.font-size(body-sm);
}
// ── 색상 칩 ─────────────────────────────────────────────────────────────────
// 값 앞에 붙는 16px 정사각형. 16px는 `icon-3`(1.6rem)과 같은 값이라 리터럴 대신
// 토큰을 쓴다.
// 테두리는 box-shadow로 그린다 — border는 칩 안쪽을 먹어 색이 보이는 면적을 줄인다.
.colorChip {
display: inline-block;
inline-size: fox.icon(3);
block-size: fox.icon(3);
margin-inline-end: fox.gap(3);
box-shadow: 0 0 0 fox.border(1) #{fox.color(border-neutral-overlay)};
vertical-align: middle;
}
.tokenName {
display: block;
color: fox.color(font-neutral-default);
font-size: fox.font-size(label-sm);
font-weight: fox.font-weight(medium);
word-break: break-all;
}
// 다른 토큰을 가리키는 값. 실제 값과 구분되어야 "이게 참조구나"가 한눈에 보인다.
.aliasRef {
color: fox.color(font-theme-primary);
}
// 서체 토큰 미리보기 문장.
.demoType {
margin: 0;
color: fox.color(font-neutral-default);
white-space: nowrap;
}
// ── 값 테이블 ───────────────────────────────────────────────────────────────
.table {
inline-size: 100%;
border-collapse: collapse;
text-align: start;
}
.table th,
.table td {
padding: fox.padding(3) fox.padding(5);
border-block-end: fox.border(1) solid fox.color(border-neutral-subtle);
}
.table th {
color: fox.color(font-neutral-subtler);
font-size: fox.font-size(label-xsm);
font-weight: fox.font-weight(medium);
}
.table td {
color: fox.color(font-neutral-default);
font-size: fox.font-size(body-sm);
}
.tdValue {
color: fox.color(font-neutral-subtle);
}
// 미리보기 칸은 내용만큼만 차지하게 두어 토큰/값 열이 밀리지 않게 한다.
.tdDemo {
inline-size: 1%;
white-space: nowrap;
}
// ── 패딩 · 간격 미리보기 ────────────────────────────────────────────────────
// 점선 테두리와 옅은 바탕이 "토큰이 만든 공간"이고, 흰 칩이 문단이다.
.demoPadBox,
.demoGapBox {
border: fox.border(1) dashed fox.color(border-theme-primary);
background: fox.color(surface-theme-primary);
}
.demoPadBox {
display: inline-block;
}
.demoGapBox {
display: inline-flex;
align-items: center;
}
.demoText {
margin: 0;
padding: fox.padding(1) fox.padding(3);
background: fox.color(surface-neutral-default);
color: fox.color(font-neutral-default);
font-size: fox.font-size(label-xsm);
white-space: nowrap;
}
// ── 모서리 반경 · 테두리 두께 미리보기 ──────────────────────────────────────
.demoRadius,
.demoBorder {
inline-size: fox.icon(7);
block-size: fox.icon(7);
border-color: fox.color(border-theme-primary);
border-style: solid;
background: fox.color(surface-neutral-gray);
}
.demoRadius {
border-width: fox.border(2);
}
// ── 아이콘 크기 미리보기 ────────────────────────────────────────────────────
// 브라우저 기본 체크박스를 토큰 크기로 키운다 — 별도 에셋 없이 크기만 비교한다.
.demoIcon {
display: block;
margin: 0;
accent-color: fox.color(surface-theme-primary);
}
// ── 그림자 미리보기 ─────────────────────────────────────────────────────────
// 그림자 색은 Figma에 토큰이 없어 중립 알파 토큰을 데모용으로 쓴다.
// 인라인 style이 참조할 수 있도록 여기서 커스텀 프로퍼티로 내려둔다.
.demoShadow {
--demo-shadow-color: #{fox.color(alpha-default-inverse25)};
inline-size: fox.icon(7);
block-size: fox.icon(6);
border-radius: fox.radius(2);
background: fox.color(surface-neutral-default);
}
// ── 배경 흐림 미리보기 ──────────────────────────────────────────────────────
// 캔버스 크기는 디자인 값이 아니라 데모용 지면이라 토큰을 두지 않는다.
.demoBackdrop {
position: relative;
overflow: hidden;
inline-size: 20rem;
block-size: 12rem;
border-radius: fox.radius(2);
}
.demoBackdropImage {
display: block;
inline-size: 100%;
block-size: 100%;
object-fit: cover;
}
.demoBackdropOverlay {
position: absolute;
inset-block: 0;
inset-inline-end: 0;
inline-size: 50%;
border-inline-start: fox.border(1) solid fox.color(border-neutral-overlay);
}
// ── 컴포넌트 예제 ───────────────────────────────────────────────────────────
.variantList {
display: flex;
flex-direction: column;
gap: fox.gap(5);
}
.variant {
display: flex;
flex-direction: column;
gap: fox.gap(2);
}
.variantLabel {
color: fox.color(font-neutral-subtler);
font-size: fox.font-size(label-xsm);
font-weight: fox.font-weight(medium);
}
// 예제를 얹는 무대. 컴포넌트 자체의 배경·여백과 섞이지 않도록 중립 표면 위에 올린다.
.variantStage {
display: flex;
flex-wrap: wrap;
gap: fox.gap(4);
align-items: center;
padding: fox.padding(7);
border: fox.border(1) solid fox.color(border-neutral-subtle);
border-radius: fox.radius(3);
background: fox.color(surface-neutral-default);
}
// 그룹 예제용. 그룹이 블록 레벨이라 정렬·줄바꿈을 보려면 폭이 정해진 무대가 필요하다.
//
// ⚠️ 세로 flex의 기본 `align-items: stretch`가 자식을 가로로 늘린다. 인라인 컨트롤
// (`inline-flex`·`inline-block`인 것들)을 여러 줄로 쌓아 비교할 때는 아래 `demoStackStart`를
// 쓴다 — 이걸 쓰면 컴포넌트가 제 폭을 잃어 실제 모습과 다르게 보인다.
.demoStack {
display: flex;
flex-direction: column;
gap: fox.gap(4);
inline-size: 100%;
}
// 인라인 컨트롤을 세로로 쌓아 비교할 때. 자식이 늘어나지 않아 제 폭 그대로 보인다.
.demoStackStart {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: fox.gap(4);
}
.demoOutline {
padding: fox.padding(3);
border: fox.border(1) dashed fox.color(border-neutral-subtle);
border-radius: fox.radius(2);
inline-size: 100%;
}
// 폼 컨트롤 예제용. FoxSelect는 폭을 부모가 정하므로 무대가 폭을 줘야 크기 비교가 된다.
// 칸 너비는 디자인 값이 아니라 데모용 지면이라 토큰을 두지 않는다.
.demoFields {
display: flex;
flex-wrap: wrap;
gap: fox.gap(5);
align-items: start;
inline-size: 100%;
> * {
inline-size: 28rem;
max-inline-size: 100%;
}
}
// 옵션 목록 단독 예제용. 목록이 `position: absolute`라 위치 기준이 필요하고, 자기 높이를
// 차지하지 않으므로 아래쪽 여백으로 펼쳐질 자리를 만들어 준다. 높이 0이면 목록이 무대
// 맨 위에서 시작한다. 치수는 데모용 지면이라 토큰을 두지 않는다.
.demoListboxStage {
position: relative;
inline-size: 28rem;
max-inline-size: 100%;
block-size: 0;
margin-block-end: 22rem;
}
// wrap 예제는 자식이 확실히 넘치도록 좁아야 한다.
.demoNarrow {
padding: fox.padding(3);
border: fox.border(1) dashed fox.color(border-neutral-subtle);
border-radius: fox.radius(2);
inline-size: 20rem;
max-inline-size: 100%;
}
// 입력 예제용. 시안 폭(360px)에 맞춰야 실제 비율로 보인다.
.demoField {
inline-size: 36rem;
max-inline-size: 100%;
}
// 이메일은 세 칸이라 360px에서는 계속 줄이 넘어간다. 넓게 볼 무대를 따로 둔다.
.demoFieldWide {
inline-size: 80rem;
max-inline-size: 100%;
}
// 캐러셀 조각은 흰 알약이라 흰 바탕에서는 보이지 않는다 — 시안처럼 어두운 판 위에 올린다.
.carouselStage {
display: flex;
flex-wrap: wrap;
gap: fox.gap(5);
align-items: center;
padding: fox.padding(6);
border-radius: fox.radius(3);
background: fox.color(surface-neutral-gray-stronger);
}
// 조합을 한 줄에 늘어놓고 비교할 때. 좁아지면 줄이 넘어간다.
.demoRow {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: fox.gap(3);
}
// 예시 하나에 이름표를 붙여 세로로 세운다 — 아이콘처럼 무엇인지 글자로 알려야 하는 자리용.
.demoLabelled {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: fox.gap(2);
color: fox.color(font-neutral-subtle);
font-size: fox.font-size(body-xsm);
}
// 정렬 가능한 머리칸 — 글자 옆에 위아래 화살표가 붙는다.
.tableSortHead {
display: inline-flex;
align-items: center;
gap: fox.gap(2);
white-space: nowrap;
> svg {
inline-size: fox.icon(3);
block-size: fox.icon(3);
color: fox.color(icon-neutral-default);
}
}