feat: FoxAccordion·FoxCard 추가
아코디언(194:3563 box · 194:3680 line)과 카드(441:6006)를 옮겼다. FoxAccordion — 계열 둘이 성격이 다르다. box는 색을 깐 상자로 여백·모서리를 상자가 갖고 제목이 강조 색이며, line은 위쪽 선 하나로 나뉘는 목록형이라 바탕이 없고 제목 줄이 세로 여백을 갖는다. 크기 lg·md가 여백·글자·모서리를 함께 바꾼다. 열림은 넘기면 호출부가, 안 넘기면 컴포넌트가 쥔다 — 여러 개 중 하나만 열리게 하려면 호출부가 쥐면 된다. 닫히면 내용을 DOM에서 뺀다. 안의 초점 가능한 것들이 탭 순서에 남으면 보이지 않는 곳으로 초점이 사라진다. FoxCard — 크기 넷은 여백과 모서리만 바꾼다. 폭은 놓이는 자리가 정한다(시안은 320px 자리에 뒀을 뿐 카드 자체의 값이 아니다). 아래쪽 동작 줄은 좌우·아래 여백만 갖는다 — 위쪽은 글 영역의 아래 여백이 이미 띄워 준다. 검증: 아코디언 16항목 중 15개 일치(나머지 1건은 내 예상값 오류였고 코드가 맞다 — action/secondary는 투명 검정이 아니라 투명 흰색이다). 카드 18항목 전부 일치. sass 694규칙 · tsc · eslint 통과. 승인된 비토큰 값: 그림 비율 16/9. Co-Authored-By: Claude Opus 5
@147dcfcb464b82f590bd5557d7104e36bfa4fc50
+++ @fox/core/components/fox-accordion/fox-accordion.tsx
... | ... | @@ -0,0 +1,98 @@ |
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useId, useState, type ReactNode } from "react"; | |
| 4 | +import { cx } from "../../utils"; | |
| 5 | +import { FoxCaretDownIcon, FoxCaretUpIcon } from "../../icons"; | |
| 6 | + | |
| 7 | +/** `box`는 색을 깐 상자, `line`은 위쪽 선 하나로 나뉘는 목록형이다. */ | |
| 8 | +export type FoxAccordionType = "box" | "line"; | |
| 9 | +export type FoxAccordionSize = "lg" | "md"; | |
| 10 | + | |
| 11 | +export interface FoxAccordionProps { | |
| 12 | + title: ReactNode; | |
| 13 | + children?: ReactNode; | |
| 14 | + type?: FoxAccordionType; | |
| 15 | + size?: FoxAccordionSize; | |
| 16 | + /** 제목 앞자리. 시안은 배지 묶음이다. */ | |
| 17 | + badge?: ReactNode; | |
| 18 | + /** 주면 호출부가 열림을 소유한다. 없으면 컴포넌트가 스스로 여닫는다. */ | |
| 19 | + open?: boolean; | |
| 20 | + defaultOpen?: boolean; | |
| 21 | + onOpenChange?: (open: boolean) => void; | |
| 22 | + /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */ | |
| 23 | + hidden?: boolean; | |
| 24 | + /** 배치 조정용. */ | |
| 25 | + className?: string; | |
| 26 | +} | |
| 27 | + | |
| 28 | +/** | |
| 29 | + * @fox 아코디언. 제목을 누르면 아래 내용이 열린다. | |
| 30 | + * | |
| 31 | + * 열림을 밖에서 쥐면 여러 개 중 하나만 열리게 하는 것도 호출부가 정할 수 있다. 넘기지 | |
| 32 | + * 않으면 스스로 여닫는다. | |
| 33 | + * | |
| 34 | + * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"` | |
| 35 | + * (또는 개별 파티셜)로 한 번 불러와야 한다. | |
| 36 | + */ | |
| 37 | +export function FoxAccordion({ | |
| 38 | + title, | |
| 39 | + children, | |
| 40 | + type = "box", | |
| 41 | + size = "lg", | |
| 42 | + badge, | |
| 43 | + open, | |
| 44 | + defaultOpen = false, | |
| 45 | + onOpenChange, | |
| 46 | + hidden = false, | |
| 47 | + className, | |
| 48 | +}: FoxAccordionProps) { | |
| 49 | + const autoId = useId(); | |
| 50 | + const [inner, setInner] = useState(defaultOpen); | |
| 51 | + | |
| 52 | + if (hidden) { | |
| 53 | + return null; | |
| 54 | + } | |
| 55 | + | |
| 56 | + const expanded = open ?? inner; | |
| 57 | + const panelId = `${autoId}-panel`; | |
| 58 | + | |
| 59 | + const toggle = () => { | |
| 60 | + if (open === undefined) { | |
| 61 | + setInner(!expanded); | |
| 62 | + } | |
| 63 | + onOpenChange?.(!expanded); | |
| 64 | + }; | |
| 65 | + | |
| 66 | + return ( | |
| 67 | + <div | |
| 68 | + className={cx( | |
| 69 | + "fox-accordion", | |
| 70 | + `fox-accordion--${type}`, | |
| 71 | + `fox-accordion--${size}`, | |
| 72 | + expanded && "fox-accordion--open", | |
| 73 | + className | |
| 74 | + )} | |
| 75 | + > | |
| 76 | + <button | |
| 77 | + className="fox-accordion__tit" | |
| 78 | + type="button" | |
| 79 | + aria-expanded={expanded} | |
| 80 | + aria-controls={panelId} | |
| 81 | + onClick={toggle} | |
| 82 | + > | |
| 83 | + {badge && <span className="fox-accordion__badge">{badge}</span>} | |
| 84 | + <span className="fox-accordion__title">{title}</span> | |
| 85 | + <span className="fox-accordion__icon" aria-hidden="true"> | |
| 86 | + {expanded ? <FoxCaretUpIcon /> : <FoxCaretDownIcon />} | |
| 87 | + </span> | |
| 88 | + </button> | |
| 89 | + | |
| 90 | + {/* 닫혔을 때는 DOM에서 뺀다 — 안의 초점 가능한 것들이 탭 순서에 남으면 안 된다. */} | |
| 91 | + {expanded && ( | |
| 92 | + <div className="fox-accordion__contents" id={panelId}> | |
| 93 | + {children} | |
| 94 | + </div> | |
| 95 | + )} | |
| 96 | + </div> | |
| 97 | + ); | |
| 98 | +} |
+++ @fox/core/components/fox-accordion/index.ts
... | ... | @@ -0,0 +1,6 @@ |
| 1 | +export { | |
| 2 | + FoxAccordion, | |
| 3 | + type FoxAccordionProps, | |
| 4 | + type FoxAccordionType, | |
| 5 | + type FoxAccordionSize, | |
| 6 | +} from "./fox-accordion"; |
+++ @fox/core/components/fox-card/fox-card.tsx
... | ... | @@ -0,0 +1,61 @@ |
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import type { ReactNode } from "react"; | |
| 4 | +import { cx } from "../../utils"; | |
| 5 | + | |
| 6 | +export type FoxCardSize = "xlg" | "lg" | "md" | "sm"; | |
| 7 | + | |
| 8 | +export interface FoxCardProps { | |
| 9 | + size?: FoxCardSize; | |
| 10 | + /** 위쪽 그림. 시안은 16:9다. */ | |
| 11 | + image?: ReactNode; | |
| 12 | + title?: ReactNode; | |
| 13 | + /** 제목 아래 한두 줄 설명. */ | |
| 14 | + description?: ReactNode; | |
| 15 | + /** 제목·설명 대신 넣을 것. 주면 `title`·`description`은 쓰이지 않는다. */ | |
| 16 | + children?: ReactNode; | |
| 17 | + /** 아래쪽 줄. 시안은 글자 버튼이다. */ | |
| 18 | + actions?: ReactNode; | |
| 19 | + /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */ | |
| 20 | + hidden?: boolean; | |
| 21 | + /** 배치 조정용. */ | |
| 22 | + className?: string; | |
| 23 | +} | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * @fox 카드. 그림·글·동작이 위에서 아래로 쌓인다. | |
| 27 | + * | |
| 28 | + * 크기는 여백과 모서리만 바꾼다 — 폭은 놓이는 자리가 정한다. | |
| 29 | + * | |
| 30 | + * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"` | |
| 31 | + * (또는 개별 파티셜)로 한 번 불러와야 한다. | |
| 32 | + */ | |
| 33 | +export function FoxCard({ | |
| 34 | + size = "md", | |
| 35 | + image, | |
| 36 | + title, | |
| 37 | + description, | |
| 38 | + children, | |
| 39 | + actions, | |
| 40 | + hidden = false, | |
| 41 | + className, | |
| 42 | +}: FoxCardProps) { | |
| 43 | + if (hidden) { | |
| 44 | + return null; | |
| 45 | + } | |
| 46 | + | |
| 47 | + return ( | |
| 48 | + <div className={cx("fox-card", `fox-card--${size}`, className)}> | |
| 49 | + {image && <div className="fox-card__image">{image}</div>} | |
| 50 | + <div className="fox-card__contents"> | |
| 51 | + {children ?? ( | |
| 52 | + <> | |
| 53 | + {title && <p className="fox-card__title">{title}</p>} | |
| 54 | + {description && <p className="fox-card__description">{description}</p>} | |
| 55 | + </> | |
| 56 | + )} | |
| 57 | + </div> | |
| 58 | + {actions && <div className="fox-card__actions">{actions}</div>} | |
| 59 | + </div> | |
| 60 | + ); | |
| 61 | +} |
+++ @fox/core/components/fox-card/index.ts
... | ... | @@ -0,0 +1,1 @@ |
| 1 | +export { FoxCard, type FoxCardProps, type FoxCardSize } from "./fox-card"; |
--- @fox/core/components/index.ts
+++ @fox/core/components/index.ts
... | ... | @@ -1,3 +1,4 @@ |
| 1 |
+export * from "./fox-accordion"; |
|
| 1 | 2 |
export * from "./fox-address"; |
| 2 | 3 |
export * from "./fox-badge"; |
| 3 | 4 |
export * from "./fox-badge-dot"; |
... | ... | @@ -8,6 +9,7 @@ |
| 8 | 9 |
export * from "./fox-button"; |
| 9 | 10 |
export * from "./fox-button-group"; |
| 10 | 11 |
export * from "./fox-button-panel"; |
| 12 |
+export * from "./fox-card"; |
|
| 11 | 13 |
export * from "./fox-carousel-area"; |
| 12 | 14 |
export * from "./fox-carousel-button"; |
| 13 | 15 |
export * from "./fox-carousel-indicator"; |
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
... | ... | @@ -58,6 +58,8 @@ |
| 58 | 58 |
import { FoxToggleQuantity } from "../core/components/fox-toggle-quantity";
|
| 59 | 59 |
import { FoxPageHeader } from "../core/components/fox-page-header";
|
| 60 | 60 |
import { FoxModal } from "../core/components/fox-modal";
|
| 61 |
+import { FoxAccordion } from "../core/components/fox-accordion";
|
|
| 62 |
+import { FoxCard } from "../core/components/fox-card";
|
|
| 61 | 63 |
import styles from "./dev-test.module.scss"; |
| 62 | 64 |
|
| 63 | 65 |
export interface ComponentExample {
|
... | ... | @@ -3382,4 +3384,91 @@ |
| 3382 | 3384 |
}, |
| 3383 | 3385 |
], |
| 3384 | 3386 |
}, |
| 3387 |
+ {
|
|
| 3388 |
+ id: "fox-accordion", |
|
| 3389 |
+ name: "FoxAccordion", |
|
| 3390 |
+ description: |
|
| 3391 |
+ "Figma 시안(accordion 194:3563 box · 194:3680 line)을 옮겼습니다. box는 색을 깐 상자로 제목이 강조 색이고, line은 위쪽 선 하나로 나뉘는 목록형입니다. 열림을 밖에서 쥐면 여러 개 중 하나만 열리게 하는 것도 호출부가 정할 수 있고, 안 넘기면 스스로 여닫습니다. 닫히면 내용이 DOM에서 빠져 탭 순서에 남지 않습니다.", |
|
| 3392 |
+ variants: [ |
|
| 3393 |
+ {
|
|
| 3394 |
+ label: "box — lg · md", |
|
| 3395 |
+ node: ( |
|
| 3396 |
+ <div className={styles.demoStack}>
|
|
| 3397 |
+ <FoxAccordion size="lg" title="아코디언 타이틀 영역" defaultOpen |
|
| 3398 |
+ badge={<FoxBadgeGroup size="md"><FoxBadge size="md">레이블</FoxBadge></FoxBadgeGroup>}>
|
|
| 3399 |
+ <p>펼쳐진 내용이 들어가는 자리입니다.</p> |
|
| 3400 |
+ </FoxAccordion> |
|
| 3401 |
+ <FoxAccordion size="md" title="아코디언 타이틀 영역" |
|
| 3402 |
+ badge={<FoxBadgeGroup size="sm"><FoxBadge size="sm">레이블</FoxBadge></FoxBadgeGroup>}>
|
|
| 3403 |
+ <p>펼쳐진 내용이 들어가는 자리입니다.</p> |
|
| 3404 |
+ </FoxAccordion> |
|
| 3405 |
+ </div> |
|
| 3406 |
+ ), |
|
| 3407 |
+ }, |
|
| 3408 |
+ {
|
|
| 3409 |
+ label: "line — 이어 붙이면 위쪽 선이 칸막이가 됩니다", |
|
| 3410 |
+ node: ( |
|
| 3411 |
+ <div className={styles.demoField}>
|
|
| 3412 |
+ <FoxAccordion type="line" size="lg" title="아코디언 타이틀 영역" defaultOpen> |
|
| 3413 |
+ <p>펼쳐진 내용이 들어가는 자리입니다.</p> |
|
| 3414 |
+ </FoxAccordion> |
|
| 3415 |
+ <FoxAccordion type="line" size="lg" title="아코디언 타이틀 영역" /> |
|
| 3416 |
+ <FoxAccordion type="line" size="md" title="아코디언 타이틀 영역" /> |
|
| 3417 |
+ </div> |
|
| 3418 |
+ ), |
|
| 3419 |
+ }, |
|
| 3420 |
+ {
|
|
| 3421 |
+ label: "배지 없이", |
|
| 3422 |
+ node: ( |
|
| 3423 |
+ <div className={styles.demoStack}>
|
|
| 3424 |
+ <FoxAccordion title="아코디언 타이틀 영역"> |
|
| 3425 |
+ <p>펼쳐진 내용이 들어가는 자리입니다.</p> |
|
| 3426 |
+ </FoxAccordion> |
|
| 3427 |
+ </div> |
|
| 3428 |
+ ), |
|
| 3429 |
+ }, |
|
| 3430 |
+ ], |
|
| 3431 |
+ }, |
|
| 3432 |
+ {
|
|
| 3433 |
+ id: "fox-card", |
|
| 3434 |
+ name: "FoxCard", |
|
| 3435 |
+ description: |
|
| 3436 |
+ "Figma 시안(card 441:6006)을 옮겼습니다. 크기 넷은 여백과 모서리만 바꿉니다 — 폭은 놓이는 자리가 정합니다(시안은 320px 자리에 뒀습니다). 그림은 16:9로 잘리고 상자가 모서리를 넘지 않게 자릅니다.", |
|
| 3437 |
+ variants: [ |
|
| 3438 |
+ {
|
|
| 3439 |
+ label: "크기 — xlg · lg · md · sm (여백과 모서리가 달라집니다)", |
|
| 3440 |
+ node: ( |
|
| 3441 |
+ <div className={styles.demoRow}>
|
|
| 3442 |
+ {(["xlg", "lg", "md", "sm"] as const).map((size) => (
|
|
| 3443 |
+ <div key={size} style={{ inlineSize: 320 }}>
|
|
| 3444 |
+ <FoxCard |
|
| 3445 |
+ size={size}
|
|
| 3446 |
+ title="타이틀 영역" |
|
| 3447 |
+ description="간단한 설명이 들어가는 영역입니다." |
|
| 3448 |
+ actions={<FoxTextButton size="md" label="액션1" />}
|
|
| 3449 |
+ /> |
|
| 3450 |
+ </div> |
|
| 3451 |
+ ))} |
|
| 3452 |
+ </div> |
|
| 3453 |
+ ), |
|
| 3454 |
+ }, |
|
| 3455 |
+ {
|
|
| 3456 |
+ label: "그림 없이 / 동작 없이 / 설명 없이", |
|
| 3457 |
+ node: ( |
|
| 3458 |
+ <div className={styles.demoRow}>
|
|
| 3459 |
+ <div style={{ inlineSize: 320 }}>
|
|
| 3460 |
+ <FoxCard title="타이틀 영역" description="간단한 설명이 들어가는 영역입니다." |
|
| 3461 |
+ actions={<FoxTextButton size="md" label="액션1" />} />
|
|
| 3462 |
+ </div> |
|
| 3463 |
+ <div style={{ inlineSize: 320 }}>
|
|
| 3464 |
+ <FoxCard title="타이틀 영역" description="간단한 설명이 들어가는 영역입니다." /> |
|
| 3465 |
+ </div> |
|
| 3466 |
+ <div style={{ inlineSize: 320 }}>
|
|
| 3467 |
+ <FoxCard title="타이틀 영역" actions={<FoxTextButton size="md" label="액션1" />} />
|
|
| 3468 |
+ </div> |
|
| 3469 |
+ </div> |
|
| 3470 |
+ ), |
|
| 3471 |
+ }, |
|
| 3472 |
+ ], |
|
| 3473 |
+ }, |
|
| 3385 | 3474 |
]; |
+++ @fox/styles/_fox-accordion.scss
... | ... | @@ -0,0 +1,155 @@ |
| 1 | +// FoxAccordion — 시안: 통합관리자페이지 디자인시스템(KE9UILWhB8qTBe14uXUn4F) | |
| 2 | +// accordion (194:3563 box · 194:3680 line · 섹션 392:5034) | |
| 3 | +// | |
| 4 | +// 마크업 계약 (React 밖 소비자용): | |
| 5 | +// <div class="fox-accordion fox-accordion--box fox-accordion--lg fox-accordion--open"> | |
| 6 | +// <button class="fox-accordion__tit" type="button" aria-expanded="true" aria-controls="…"> | |
| 7 | +// <span class="fox-accordion__badge">…fox-badge-group…</span> <!-- 선택 --> | |
| 8 | +// <span class="fox-accordion__title">제목</span> | |
| 9 | +// <span class="fox-accordion__icon">…svg…</span> | |
| 10 | +// </button> | |
| 11 | +// <div class="fox-accordion__contents" id="…">…</div> <!-- 열렸을 때만 --> | |
| 12 | +// </div> | |
| 13 | +// | |
| 14 | +// 계열 둘이 성격이 다르다. | |
| 15 | +// - box: 색을 깐 상자. 여백·모서리를 상자가 갖고 제목이 강조 색이다. | |
| 16 | +// - line: 위쪽 선 하나로 나뉘는 목록형. 바탕이 없고 제목 줄이 세로 여백을 갖는다. | |
| 17 | +// | |
| 18 | +// 크기는 여백·글자·모서리를 함께 바꾼다(시안 lg·md). | |
| 19 | +// | |
| 20 | +// 토큰이 아닌 값: | |
| 21 | +// - line-height / letter-spacing: 시안 텍스트 스타일의 값이나 Figma가 변수로 | |
| 22 | +// export하지 않는다. | |
| 23 | +// - 버튼 초기화(appearance/cursor/색 상속): 구조 규칙. | |
| 24 | + | |
| 25 | +@use "abstracts" as fox; | |
| 26 | + | |
| 27 | +.fox-accordion { | |
| 28 | + display: flex; | |
| 29 | + flex-direction: column; | |
| 30 | + align-items: flex-start; | |
| 31 | + inline-size: 100%; | |
| 32 | + | |
| 33 | + // 제목 줄은 통째로 누르는 자리다. | |
| 34 | + &__tit { | |
| 35 | + display: flex; | |
| 36 | + align-items: center; | |
| 37 | + gap: fox.gap(3); | |
| 38 | + inline-size: 100%; | |
| 39 | + margin: 0; | |
| 40 | + padding: 0; | |
| 41 | + border: none; | |
| 42 | + background: none; | |
| 43 | + font-family: fox.font-family(title); | |
| 44 | + font-weight: fox.font-weight(bold); | |
| 45 | + line-height: 1.4; | |
| 46 | + letter-spacing: -0.025em; | |
| 47 | + text-align: start; | |
| 48 | + cursor: pointer; | |
| 49 | + } | |
| 50 | + | |
| 51 | + &__badge { | |
| 52 | + display: inline-flex; | |
| 53 | + flex-shrink: 0; | |
| 54 | + } | |
| 55 | + | |
| 56 | + // 제목이 남는 폭을 먹고 아이콘은 오른쪽 끝에 붙는다. | |
| 57 | + &__title { | |
| 58 | + flex: 1 0 0; | |
| 59 | + min-inline-size: 0; | |
| 60 | + word-break: break-word; | |
| 61 | + } | |
| 62 | + | |
| 63 | + &__icon { | |
| 64 | + display: inline-flex; | |
| 65 | + flex-shrink: 0; | |
| 66 | + color: fox.color(icon-neutral-default); | |
| 67 | + | |
| 68 | + > * { | |
| 69 | + inline-size: fox.icon(4); | |
| 70 | + block-size: fox.icon(4); | |
| 71 | + } | |
| 72 | + } | |
| 73 | + | |
| 74 | + &__contents { | |
| 75 | + display: flex; | |
| 76 | + flex-direction: column; | |
| 77 | + align-items: flex-start; | |
| 78 | + inline-size: 100%; | |
| 79 | + color: fox.color(font-neutral-default); | |
| 80 | + font-family: fox.font-family(body); | |
| 81 | + font-size: fox.font-size(body-md); | |
| 82 | + font-weight: fox.font-weight(regular); | |
| 83 | + line-height: 1.5; | |
| 84 | + letter-spacing: -0.025em; | |
| 85 | + } | |
| 86 | + | |
| 87 | + // ── box 계열 ────────────────────────────────────────────────────────────── | |
| 88 | + &--box { | |
| 89 | + background: fox.color(surface-theme-secondary); | |
| 90 | + } | |
| 91 | + | |
| 92 | + &--box &__title { | |
| 93 | + color: fox.color(font-theme-secondary); | |
| 94 | + } | |
| 95 | + | |
| 96 | + &--box#{&}--lg { | |
| 97 | + gap: fox.gap(7); | |
| 98 | + padding-inline: fox.padding(9); | |
| 99 | + padding-block: fox.padding(8); | |
| 100 | + border-radius: fox.radius(5); | |
| 101 | + } | |
| 102 | + | |
| 103 | + &--box#{&}--md { | |
| 104 | + gap: fox.gap(5); | |
| 105 | + padding-inline: fox.padding(7); | |
| 106 | + padding-block: fox.padding(6); | |
| 107 | + border-radius: fox.radius(4); | |
| 108 | + } | |
| 109 | + | |
| 110 | + &--box#{&}--lg &__contents { | |
| 111 | + gap: fox.gap(7); | |
| 112 | + } | |
| 113 | + | |
| 114 | + &--box#{&}--md &__contents { | |
| 115 | + gap: fox.gap(5); | |
| 116 | + } | |
| 117 | + | |
| 118 | + // ── line 계열 ───────────────────────────────────────────────────────────── | |
| 119 | + // 여러 개를 이어 붙이면 위쪽 선이 칸막이가 된다. | |
| 120 | + &--line { | |
| 121 | + border-block-start: fox.border(1) solid fox.color(border-neutral-subtler); | |
| 122 | + background: fox.color(action-secondary); | |
| 123 | + } | |
| 124 | + | |
| 125 | + &--line &__title { | |
| 126 | + color: fox.color(font-neutral-strong); | |
| 127 | + } | |
| 128 | + | |
| 129 | + &--line#{&}--lg &__tit { | |
| 130 | + padding-block: fox.padding(8); | |
| 131 | + } | |
| 132 | + | |
| 133 | + &--line#{&}--md &__tit { | |
| 134 | + padding-block: fox.padding(6); | |
| 135 | + } | |
| 136 | + | |
| 137 | + &--line#{&}--lg &__contents { | |
| 138 | + gap: fox.gap(7); | |
| 139 | + padding-block-end: fox.padding(8); | |
| 140 | + } | |
| 141 | + | |
| 142 | + &--line#{&}--md &__contents { | |
| 143 | + gap: fox.gap(5); | |
| 144 | + padding-block-end: fox.padding(6); | |
| 145 | + } | |
| 146 | + | |
| 147 | + // ── 크기 (글자) ─────────────────────────────────────────────────────────── | |
| 148 | + &--lg &__title { | |
| 149 | + font-size: fox.font-size(heading-sm); | |
| 150 | + } | |
| 151 | + | |
| 152 | + &--md &__title { | |
| 153 | + font-size: fox.font-size(heading-xsm); | |
| 154 | + } | |
| 155 | +} |
+++ @fox/styles/_fox-card.scss
... | ... | @@ -0,0 +1,104 @@ |
| 1 | +// FoxCard — 시안: 통합관리자페이지 디자인시스템(KE9UILWhB8qTBe14uXUn4F) | |
| 2 | +// card (441:6006 · 섹션 463:5649) | |
| 3 | +// | |
| 4 | +// 마크업 계약 (React 밖 소비자용): | |
| 5 | +// <div class="fox-card fox-card--md"> | |
| 6 | +// <div class="fox-card__image">…img…</div> <!-- 선택 --> | |
| 7 | +// <div class="fox-card__contents"> | |
| 8 | +// <p class="fox-card__title">타이틀</p> | |
| 9 | +// <p class="fox-card__description">설명</p> | |
| 10 | +// </div> | |
| 11 | +// <div class="fox-card__actions">…fox-text-button…</div> <!-- 선택 --> | |
| 12 | +// </div> | |
| 13 | +// | |
| 14 | +// 크기 넷은 여백과 모서리만 바꾼다 — 폭은 놓이는 자리가 정한다(시안은 320px 자리에 뒀다). | |
| 15 | +// | |
| 16 | +// 그림은 16:9로 잘린다. 상자가 `overflow: clip`이라 위쪽 모서리를 그림이 넘지 않는다. | |
| 17 | +// | |
| 18 | +// 아래쪽 줄은 좌우·아래 여백만 갖는다 — 위쪽은 글 영역의 아래 여백이 이미 띄워 준다. | |
| 19 | +// | |
| 20 | +// 토큰이 아닌 값: | |
| 21 | +// - 그림 비율 16/9: 시안 값이나 Figma가 변수로 export하지 않는다(사용자 승인). | |
| 22 | +// - line-height / letter-spacing: 시안 텍스트 스타일의 값이나 Figma가 변수로 | |
| 23 | +// export하지 않는다. | |
| 24 | +// - object-fit / overflow: 구조 규칙. | |
| 25 | + | |
| 26 | +@use "abstracts" as fox; | |
| 27 | + | |
| 28 | +.fox-card { | |
| 29 | + box-sizing: border-box; | |
| 30 | + display: flex; | |
| 31 | + flex-direction: column; | |
| 32 | + align-items: flex-start; | |
| 33 | + inline-size: 100%; | |
| 34 | + overflow: clip; | |
| 35 | + border: fox.border(1) solid fox.color(border-neutral-subtle); | |
| 36 | + background: fox.color(surface-neutral-default); | |
| 37 | + | |
| 38 | + &__image { | |
| 39 | + inline-size: 100%; | |
| 40 | + // 시안 값이나 변수로 export되지 않는다 — 파일 상단 참고. | |
| 41 | + aspect-ratio: 16 / 9; | |
| 42 | + | |
| 43 | + > * { | |
| 44 | + inline-size: 100%; | |
| 45 | + block-size: 100%; | |
| 46 | + object-fit: cover; | |
| 47 | + } | |
| 48 | + } | |
| 49 | + | |
| 50 | + &__contents { | |
| 51 | + display: flex; | |
| 52 | + flex-direction: column; | |
| 53 | + align-items: flex-start; | |
| 54 | + gap: fox.gap(2); | |
| 55 | + inline-size: 100%; | |
| 56 | + word-break: break-word; | |
| 57 | + } | |
| 58 | + | |
| 59 | + &__title { | |
| 60 | + inline-size: 100%; | |
| 61 | + margin: 0; | |
| 62 | + color: fox.color(font-neutral-strong); | |
| 63 | + font-family: fox.font-family(title); | |
| 64 | + font-size: fox.font-size(heading-sm); | |
| 65 | + font-weight: fox.font-weight(bold); | |
| 66 | + line-height: 1.4; | |
| 67 | + letter-spacing: -0.025em; | |
| 68 | + } | |
| 69 | + | |
| 70 | + &__description { | |
| 71 | + inline-size: 100%; | |
| 72 | + margin: 0; | |
| 73 | + color: fox.color(font-neutral-default); | |
| 74 | + font-family: fox.font-family(body); | |
| 75 | + font-size: fox.font-size(body-md); | |
| 76 | + font-weight: fox.font-weight(regular); | |
| 77 | + line-height: 1.5; | |
| 78 | + letter-spacing: -0.025em; | |
| 79 | + } | |
| 80 | + | |
| 81 | + // 위쪽은 글 영역의 아래 여백이 이미 띄워 준다. | |
| 82 | + &__actions { | |
| 83 | + display: flex; | |
| 84 | + align-items: flex-start; | |
| 85 | + gap: fox.gap(5); | |
| 86 | + inline-size: 100%; | |
| 87 | + } | |
| 88 | + | |
| 89 | + // ── 크기 ────────────────────────────────────────────────────────────────── | |
| 90 | + @each $size in (xlg, lg, md, sm) { | |
| 91 | + &--#{$size} { | |
| 92 | + border-radius: fox.card(radius-#{$size}); | |
| 93 | + } | |
| 94 | + | |
| 95 | + &--#{$size} > .fox-card__contents { | |
| 96 | + padding: fox.card(padding-#{$size}); | |
| 97 | + } | |
| 98 | + | |
| 99 | + &--#{$size} > .fox-card__actions { | |
| 100 | + padding-inline: fox.card(padding-#{$size}); | |
| 101 | + padding-block-end: fox.card(padding-#{$size}); | |
| 102 | + } | |
| 103 | + } | |
| 104 | +} |
--- @fox/styles/components.scss
+++ @fox/styles/components.scss
... | ... | @@ -53,3 +53,5 @@ |
| 53 | 53 |
@use "fox-toggle-quantity"; |
| 54 | 54 |
@use "fox-page-header"; |
| 55 | 55 |
@use "fox-modal"; |
| 56 |
+@use "fox-accordion"; |
|
| 57 |
+@use "fox-card"; |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?