Merge branch 'develop' of http://git.dataeum.kr/ldu0009/edupay-admin into hub
@32521b124752df5083cefea3693c9b3e68ee237c
+++ @fox/core/components/fox-step-indicator-item/fox-step-indicator-item.tsx
... | ... | @@ -0,0 +1,108 @@ |
| 1 | +import type { ReactNode, Ref } from "react"; | |
| 2 | +import { cx } from "../../utils"; | |
| 3 | + | |
| 4 | +/** 시안의 state. 지난 단계 · 지금 단계 · 아직 오지 않은 단계다. */ | |
| 5 | +export type FoxStepIndicatorItemState = "completion" | "ongoing" | "before"; | |
| 6 | + | |
| 7 | +export interface FoxStepIndicatorItemProps { | |
| 8 | + state?: FoxStepIndicatorItemState; | |
| 9 | + /** 단계 번호 줄(작고 흐린 글자). 넘기지 않으면 렌더링하지 않는다. */ | |
| 10 | + step?: ReactNode; | |
| 11 | + /** 단계 제목 줄. 넘기지 않으면 렌더링하지 않는다. */ | |
| 12 | + title?: ReactNode; | |
| 13 | + /** | |
| 14 | + * 동그라미 오른쪽으로 뻗는 선. **마지막 단계에서는 꺼야 한다** — 켜 두면 선이 묶음 밖으로 | |
| 15 | + * 삐져나간다. 선 색은 `state`가 정한다(지난 단계만 진하다). | |
| 16 | + */ | |
| 17 | + line?: boolean; | |
| 18 | + /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */ | |
| 19 | + hidden?: boolean; | |
| 20 | + id?: string; | |
| 21 | + /** 배치 조정용. 시안의 폭(80px)을 바꿔야 할 때도 여기로 준다. */ | |
| 22 | + className?: string; | |
| 23 | + ref?: Ref<HTMLDivElement>; | |
| 24 | +} | |
| 25 | + | |
| 26 | +/** `Record`로 고정해 상태를 추가하면 항목 누락이 타입 에러가 되게 한다. */ | |
| 27 | +const STATE_CLASS: Record<FoxStepIndicatorItemState, string> = { | |
| 28 | + completion: "fox-step-indicator-item--completion", | |
| 29 | + ongoing: "fox-step-indicator-item--ongoing", | |
| 30 | + before: "fox-step-indicator-item--before", | |
| 31 | +}; | |
| 32 | + | |
| 33 | +/** | |
| 34 | + * @fox 단계 하나. 위에 동그라미와 선, 아래에 단계 번호와 제목이 놓인다. | |
| 35 | + * `FoxStepIndicator` 안에 여러 개를 늘어놓아 쓴다. | |
| 36 | + * | |
| 37 | + * 상태를 갖지 않는다 — 어디까지 왔는지는 호출부가 각 단계의 `state`로 정한다. | |
| 38 | + * | |
| 39 | + * **모바일에서는 글자가 사라지고 동그라미와 선만 남는다.** 그 판단은 CSS가 한다(화면 폭) — | |
| 40 | + * 기기를 prop으로 받지 않는다. 조건부 렌더로 하면 창 폭이 바뀔 때마다 DOM이 들락거리고, | |
| 41 | + * React를 쓰지 않는 소비자는 같은 결과를 얻지 못한다(`FoxChipSelectOption`의 확인 버튼과 | |
| 42 | + * 같은 근거). | |
| 43 | + * | |
| 44 | + * ⚠️ 진행 상태를 소리로도 전해야 하면 감싼 쪽이 알린다 — 이 조각의 동그라미·선은 장식이라 | |
| 45 | + * 보조기술에 아무것도 말하지 않고, 읽히는 것은 단계 번호와 제목뿐이다. 모바일에서는 그 | |
| 46 | + * 글자마저 사라지므로, 그 화면에서 순서를 전해야 하면 호출부가 이름을 따로 준다. | |
| 47 | + * | |
| 48 | + * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"` | |
| 49 | + * (또는 개별 파티셜)로 한 번 불러와야 한다. | |
| 50 | + */ | |
| 51 | +export function FoxStepIndicatorItem({ | |
| 52 | + state = "before", | |
| 53 | + step, | |
| 54 | + title, | |
| 55 | + line = true, | |
| 56 | + hidden = false, | |
| 57 | + id, | |
| 58 | + className, | |
| 59 | + ref, | |
| 60 | +}: FoxStepIndicatorItemProps) { | |
| 61 | + if (hidden) { | |
| 62 | + return null; | |
| 63 | + } | |
| 64 | + | |
| 65 | + return ( | |
| 66 | + <div | |
| 67 | + ref={ref} | |
| 68 | + id={id} | |
| 69 | + className={cx("fox-step-indicator-item", STATE_CLASS[state], className)} | |
| 70 | + > | |
| 71 | + <div className="fox-step-indicator-item__indicator" aria-hidden="true"> | |
| 72 | + <span className="fox-step-indicator-item__mark"> | |
| 73 | + {state === "completion" ? <CheckMark /> : null} | |
| 74 | + {state === "ongoing" ? <span className="fox-step-indicator-item__dot" /> : null} | |
| 75 | + </span> | |
| 76 | + {line ? <span className="fox-step-indicator-item__line" /> : null} | |
| 77 | + </div> | |
| 78 | + | |
| 79 | + {step === undefined && title === undefined ? null : ( | |
| 80 | + <div className="fox-step-indicator-item__text"> | |
| 81 | + {step === undefined ? null : ( | |
| 82 | + <span className="fox-step-indicator-item__step">{step}</span> | |
| 83 | + )} | |
| 84 | + {title === undefined ? null : ( | |
| 85 | + <span className="fox-step-indicator-item__title">{title}</span> | |
| 86 | + )} | |
| 87 | + </div> | |
| 88 | + )} | |
| 89 | + </div> | |
| 90 | + ); | |
| 91 | +} | |
| 92 | + | |
| 93 | +// 지난 단계의 체크 표시. `@fox/core/icons`(Phosphor)를 쓰지 않는다 — 시안이 준 것은 선으로 | |
| 94 | +// 그린 10×8 글리프이고, Phosphor의 Check는 면으로 채운 다른 그림이라 크기·굵기가 맞지 않는다. | |
| 95 | +// `stroke`를 시안의 리터럴(흰색) 대신 `currentColor`로 두면 색을 상태 규칙이 정한다. | |
| 96 | +function CheckMark() { | |
| 97 | + return ( | |
| 98 | + <svg viewBox="0 0 12 10" fill="none" aria-hidden="true"> | |
| 99 | + <path | |
| 100 | + d="M0.75 4.75006L4.96726 8.75006L10.75 0.750061" | |
| 101 | + stroke="currentColor" | |
| 102 | + strokeWidth="1.5" | |
| 103 | + strokeLinecap="round" | |
| 104 | + strokeLinejoin="round" | |
| 105 | + /> | |
| 106 | + </svg> | |
| 107 | + ); | |
| 108 | +} |
+++ @fox/core/components/fox-step-indicator-item/index.ts
... | ... | @@ -0,0 +1,1 @@ |
| 1 | +export { FoxStepIndicatorItem, type FoxStepIndicatorItemProps, type FoxStepIndicatorItemState } from "./fox-step-indicator-item"; |
+++ @fox/core/components/fox-step-indicator/fox-step-indicator.tsx
... | ... | @@ -0,0 +1,56 @@ |
| 1 | +import type { ReactNode, Ref } from "react"; | |
| 2 | +import { cx } from "../../utils"; | |
| 3 | + | |
| 4 | +export interface FoxStepIndicatorProps { | |
| 5 | + /** 묶음을 읽어 줄 이름. 화면에 보이는 제목이 이미 있으면 `labelledBy`를 쓴다. */ | |
| 6 | + label?: string; | |
| 7 | + /** 묶음 이름 역할을 하는 요소의 id. `label`보다 우선한다 — 화면의 글자와 어긋나지 않는다. */ | |
| 8 | + labelledBy?: string; | |
| 9 | + /** `FoxStepIndicatorItem`들. */ | |
| 10 | + children?: ReactNode; | |
| 11 | + /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */ | |
| 12 | + hidden?: boolean; | |
| 13 | + /** 배치 조정용. 시안의 폭(400px = 80px × 5단계)을 바꿔야 할 때도 여기로 준다. */ | |
| 14 | + className?: string; | |
| 15 | + ref?: Ref<HTMLDivElement>; | |
| 16 | +} | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * @fox 단계 묶음. `FoxStepIndicatorItem`들을 가로로 늘어놓는다. 배치만 책임지고 상태를 갖지 않는다 — | |
| 20 | + * 어디까지 왔는지는 각 단계의 `state`가 말한다. | |
| 21 | + * | |
| 22 | + * 자식에게 덮어쓰는 값이 없다(`FoxTagList`·`FoxChipArea`와 다른 점이다). 단계마다 `state`가 | |
| 23 | + * 다른 것이 정상이고, **마지막 단계는 `line={false}`로 선을 꺼야 한다** — 묶음이 자식의 | |
| 24 | + * 순서를 알고 대신 꺼 주지 않는 것은, 자식을 조건부로 렌더하거나 프래그먼트로 감싸는 | |
| 25 | + * 호출부에서 "마지막"의 판단이 어긋나기 때문이다. | |
| 26 | + * | |
| 27 | + * `role="list"`를 쓰지 않는다. 목록이 아니라 하나의 진행 상황이고, 읽히는 것은 각 단계의 | |
| 28 | + * 글자다 — 진행률을 소리로 전해야 하면 호출부가 `label`에 담는다("전체 5단계 중 2단계" 등). | |
| 29 | + * | |
| 30 | + * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"` | |
| 31 | + * (또는 개별 파티셜)로 한 번 불러와야 한다. | |
| 32 | + */ | |
| 33 | +export function FoxStepIndicator({ | |
| 34 | + label, | |
| 35 | + labelledBy, | |
| 36 | + children, | |
| 37 | + hidden = false, | |
| 38 | + className, | |
| 39 | + ref, | |
| 40 | +}: FoxStepIndicatorProps) { | |
| 41 | + if (hidden) { | |
| 42 | + return null; | |
| 43 | + } | |
| 44 | + | |
| 45 | + return ( | |
| 46 | + <div | |
| 47 | + ref={ref} | |
| 48 | + role="group" | |
| 49 | + aria-label={labelledBy ? undefined : label} | |
| 50 | + aria-labelledby={labelledBy} | |
| 51 | + className={cx("fox-step-indicator", className)} | |
| 52 | + > | |
| 53 | + {children} | |
| 54 | + </div> | |
| 55 | + ); | |
| 56 | +} |
+++ @fox/core/components/fox-step-indicator/index.ts
... | ... | @@ -0,0 +1,4 @@ |
| 1 | +export { | |
| 2 | + FoxStepIndicator, | |
| 3 | + type FoxStepIndicatorProps, | |
| 4 | +} from "./fox-step-indicator"; |
--- @fox/core/components/index.ts
+++ @fox/core/components/index.ts
... | ... | @@ -47,6 +47,8 @@ |
| 47 | 47 |
export * from "./fox-snackbar-list"; |
| 48 | 48 |
export * from "./fox-spinner"; |
| 49 | 49 |
export * from "./fox-status-indicator"; |
| 50 |
+export * from "./fox-step-indicator-item"; |
|
| 51 |
+export * from "./fox-step-indicator"; |
|
| 50 | 52 |
export * from "./fox-tab"; |
| 51 | 53 |
export * from "./fox-tab-item"; |
| 52 | 54 |
export * from "./fox-table"; |
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
... | ... | @@ -57,6 +57,8 @@ |
| 57 | 57 |
import { FoxSnackbar } from "../core/components/fox-snackbar";
|
| 58 | 58 |
import { FoxSnackbarList } from "../core/components/fox-snackbar-list";
|
| 59 | 59 |
import { FoxStatusIndicator } from "../core/components/fox-status-indicator";
|
| 60 |
+import { FoxStepIndicatorItem } from "../core/components/fox-step-indicator-item";
|
|
| 61 |
+import { FoxStepIndicator } from "../core/components/fox-step-indicator";
|
|
| 60 | 62 |
import { FoxChip, type FoxChipItem } from "../core/components/fox-chip";
|
| 61 | 63 |
import { FoxChipArea } from "../core/components/fox-chip-area";
|
| 62 | 64 |
import { FoxSelectText } from "../core/components/fox-select-text";
|
... | ... | @@ -3833,4 +3835,62 @@ |
| 3833 | 3835 |
}, |
| 3834 | 3836 |
], |
| 3835 | 3837 |
}, |
| 3838 |
+ {
|
|
| 3839 |
+ id: "fox-step-indicator-item", |
|
| 3840 |
+ name: "FoxStepIndicatorItem", |
|
| 3841 |
+ description: |
|
| 3842 |
+ "Figma 시안(step 251:1539) 단계 하나입니다. 위에 동그라미와 선, 아래에 단계 번호(body/xsm)와 제목(body/sm)이 놓입니다. state 세 가지가 동그라미 안과 선 색을 정합니다: completion은 회색으로 채우고 체크를 얹으며 선도 진합니다(여기까지 왔다는 표시), ongoing은 테두리만 강조색이고 가운데 점이 자리를 채우며 선은 흐립니다, before는 흐린 테두리에 연한 면이고 안이 비었습니다. 마지막 단계는 line={false}로 선을 꺼야 합니다 — 켜 두면 묶음 밖으로 삐져나갑니다. 동그라미와 선은 aria-hidden 장식이고 읽히는 것은 글자뿐입니다. 제목 굵기는 시안이 700인데 토큰 세트에 700이 없어(400 regular · 600 medium · 800 bold) 시스템의 bold(800)로 올렸습니다 — Pretendard 기준 Bold보다 한 단계 굵은 ExtraBold입니다. 너무 굵어 보이면 medium으로 내립니다.",
|
|
| 3843 |
+ variants: [ |
|
| 3844 |
+ {
|
|
| 3845 |
+ label: "상태 3종", |
|
| 3846 |
+ node: ( |
|
| 3847 |
+ <> |
|
| 3848 |
+ <FoxStepIndicatorItem state="completion" step="STEP 01" title="약관 동의" /> |
|
| 3849 |
+ <FoxStepIndicatorItem state="ongoing" step="STEP 02" title="정보 입력" /> |
|
| 3850 |
+ <FoxStepIndicatorItem state="before" step="STEP 03" title="가입 완료" /> |
|
| 3851 |
+ </> |
|
| 3852 |
+ ), |
|
| 3853 |
+ }, |
|
| 3854 |
+ {
|
|
| 3855 |
+ label: "글자 없이 — 동그라미와 선만 (모바일에서 자동으로 이 모양이 됩니다)", |
|
| 3856 |
+ node: ( |
|
| 3857 |
+ <> |
|
| 3858 |
+ <FoxStepIndicatorItem state="completion" /> |
|
| 3859 |
+ <FoxStepIndicatorItem state="ongoing" /> |
|
| 3860 |
+ <FoxStepIndicatorItem state="before" /> |
|
| 3861 |
+ </> |
|
| 3862 |
+ ), |
|
| 3863 |
+ }, |
|
| 3864 |
+ ], |
|
| 3865 |
+ }, |
|
| 3866 |
+ {
|
|
| 3867 |
+ id: "fox-step-indicator", |
|
| 3868 |
+ name: "FoxStepIndicator", |
|
| 3869 |
+ description: |
|
| 3870 |
+ "Figma 시안(step-indicator 708:1623) FoxStepIndicatorItem들을 가로로 늘어놓는 묶음입니다. 배치만 갖고 상태는 없습니다 — 어디까지 왔는지는 각 단계의 state가 말합니다. 자식에게 덮어쓰는 값이 없어서 마지막 단계의 line={false}는 호출부가 줍니다: 묶음이 '마지막'을 대신 판단하면 자식을 조건부로 렌더하거나 프래그먼트로 감싼 호출부에서 어긋납니다. role=list가 아니라 role=group입니다 — 목록이 아니라 하나의 진행 상황이고, 진행률을 소리로 전해야 하면 label에 담습니다. ⚠️ 폭 400px은 80px × 5단계라 단계가 다섯 개일 때의 값입니다. 개수가 다르면 className으로 덮습니다. 화면을 좁히면 글자가 사라지고 동그라미 줄만 남습니다.",
|
|
| 3871 |
+ variants: [ |
|
| 3872 |
+ {
|
|
| 3873 |
+ label: "5단계 — 시안 폭(400px)에 맞는 개수", |
|
| 3874 |
+ node: ( |
|
| 3875 |
+ <FoxStepIndicator label="가입 절차 (전체 5단계 중 3단계)"> |
|
| 3876 |
+ <FoxStepIndicatorItem state="completion" step="STEP 01" title="약관 동의" /> |
|
| 3877 |
+ <FoxStepIndicatorItem state="completion" step="STEP 02" title="본인 인증" /> |
|
| 3878 |
+ <FoxStepIndicatorItem state="ongoing" step="STEP 03" title="정보 입력" /> |
|
| 3879 |
+ <FoxStepIndicatorItem state="before" step="STEP 04" title="결제 수단" /> |
|
| 3880 |
+ <FoxStepIndicatorItem state="before" step="STEP 05" title="가입 완료" line={false} />
|
|
| 3881 |
+ </FoxStepIndicator> |
|
| 3882 |
+ ), |
|
| 3883 |
+ }, |
|
| 3884 |
+ {
|
|
| 3885 |
+ label: "3단계 — 폭이 고정이라 오른쪽이 남습니다 (className으로 덮는 자리)", |
|
| 3886 |
+ node: ( |
|
| 3887 |
+ <FoxStepIndicator label="결제 절차"> |
|
| 3888 |
+ <FoxStepIndicatorItem state="completion" step="STEP 01" title="장바구니" /> |
|
| 3889 |
+ <FoxStepIndicatorItem state="ongoing" step="STEP 02" title="결제" /> |
|
| 3890 |
+ <FoxStepIndicatorItem state="before" step="STEP 03" title="완료" line={false} />
|
|
| 3891 |
+ </FoxStepIndicator> |
|
| 3892 |
+ ), |
|
| 3893 |
+ }, |
|
| 3894 |
+ ], |
|
| 3895 |
+ }, |
|
| 3836 | 3896 |
]; |
+++ @fox/styles/_fox-step-indicator-item.scss
... | ... | @@ -0,0 +1,168 @@ |
| 1 | +// FoxStepIndicatorItem — 시안: 통합관리자페이지 디자인시스템 Figma step | |
| 2 | +// | |
| 3 | +// 단계 하나. 위에 동그라미와 선(인디케이터), 아래에 단계 번호와 제목이 놓인다. | |
| 4 | +// | |
| 5 | +// 마크업 계약 (React 밖 소비자용): | |
| 6 | +// <div class="fox-step-indicator-item fox-step-indicator-item--ongoing"> | |
| 7 | +// <div class="fox-step-indicator-item__indicator" aria-hidden="true"> | |
| 8 | +// <span class="fox-step-indicator-item__mark"><span class="fox-step-indicator-item__dot"></span></span> | |
| 9 | +// <span class="fox-step-indicator-item__line"></span> <!-- 마지막 단계는 뺀다 --> | |
| 10 | +// </div> | |
| 11 | +// <div class="fox-step-indicator-item__text"> <!-- 선택 --> | |
| 12 | +// <span class="fox-step-indicator-item__step">STEP 01</span> | |
| 13 | +// <span class="fox-step-indicator-item__title">약관 동의</span> | |
| 14 | +// </div> | |
| 15 | +// </div> | |
| 16 | +// | |
| 17 | +// 상태별로 동그라미 안이 다르다: completion은 체크 글리프, ongoing은 `__dot`, before는 비었다. | |
| 18 | +// 상태 클래스가 색·테두리를 정하므로 마크업은 그대로 두고 클래스만 바꾸면 된다. | |
| 19 | +// | |
| 20 | +// **모바일에서는 `__text`가 사라진다.** 시안의 device 변형을 CSS 브레이크포인트로 옮긴 | |
| 21 | +// 것이다 — 조건부 렌더로 하면 창 폭이 바뀔 때마다 DOM이 들락거리고, React를 쓰지 않는 | |
| 22 | +// 소비자는 같은 결과를 얻지 못한다(`fox-chip-select-option`의 확인 버튼과 같은 근거). | |
| 23 | +// | |
| 24 | +// 동그라미 안쪽 여백을 시안대로 적지 않는다. 시안은 `padding: 6px 5px`로 20px 안에 10×8 | |
| 25 | +// 글리프를 밀어 넣는데, 그 값은 **가운데 정렬을 여백으로 계산한 것**이라(20-5-5=10, | |
| 26 | +// 20-6-6=8) `justify-content`·`align-items`로 바꿔 적는다. 결과는 같고 5px·6px이라는 | |
| 27 | +// 토큰 없는 값이 사라진다. | |
| 28 | +// | |
| 29 | +// 토큰이 아닌 값: | |
| 30 | +// - ⚠️ `inline-size: 6.1rem`(선 61px): 시안 값 그대로다. 토큰 스케일에 61이 없고, 동그라미 | |
| 31 | +// (20px)와 합치면 81px이라 단계 폭(80px)보다 1px 길다 — 다음 동그라미까지 틈 없이 | |
| 32 | +// 닿게 하려고 겹치는 값으로 보인다. 단계 폭을 바꾸면 이 값도 함께 봐야 한다. | |
| 33 | +// - ⚠️ `block-size: 0.15rem`(선 굵기 1.5px) / `border-width: 0.15rem`(ongoing 테두리): | |
| 34 | +// 시안 값이나 border 토큰은 1px·2px·4px뿐이라 1.5px이 없다. 토큰이 생기면 바꾼다. | |
| 35 | +// - ⚠️ 체크 글리프의 `10px × 8px`: 시안이 준 비율(5:4)이 다른 아이콘과 달라 `icon()` 스케일에 | |
| 36 | +// 없다. `number()`의 같은 값(10px·8px)으로 지목한다. | |
| 37 | +// - box-sizing / display / flex / aspect-ratio: 구조 규칙. | |
| 38 | +// | |
| 39 | +// ⚠️ 단계 제목의 굵기: 시안 CSS가 `var(--font-weight-regular, 700)`으로 이름과 값이 | |
| 40 | +// 어긋나는데, **값(굵게)이 맞다**(사용자 확인). 700은 이 토큰 세트에 없어서 | |
| 41 | +// (400 regular · 600 medium · 800 bold) 시스템의 `bold`를 쓴다 — Pretendard 기준으로 시안이 | |
| 42 | +// Bold(700)라면 한 단계 더 굵은 ExtraBold(800)로 그려진다. 너무 굵어 보이면 `medium`으로 | |
| 43 | +// 내린다. | |
| 44 | + | |
| 45 | +@use "@fox/styles/abstracts" as fox; | |
| 46 | + | |
| 47 | +.fox-step-indicator-item { | |
| 48 | + box-sizing: border-box; | |
| 49 | + display: flex; | |
| 50 | + flex-direction: column; | |
| 51 | + align-items: flex-start; | |
| 52 | + gap: fox.gap(3); | |
| 53 | + inline-size: fox.number(20); | |
| 54 | + | |
| 55 | + // ── 인디케이터(동그라미 + 선) ─────────────────────────────────────────────── | |
| 56 | + &__indicator { | |
| 57 | + display: flex; | |
| 58 | + align-items: center; | |
| 59 | + } | |
| 60 | + | |
| 61 | + // 20px 정원. 안에 든 것(체크·점)은 상태가 정하고, 여기서는 자리만 만든다. | |
| 62 | + &__mark { | |
| 63 | + box-sizing: border-box; | |
| 64 | + display: flex; | |
| 65 | + flex-shrink: 0; | |
| 66 | + align-items: center; | |
| 67 | + justify-content: center; | |
| 68 | + inline-size: fox.number(9); | |
| 69 | + block-size: fox.number(9); | |
| 70 | + aspect-ratio: 1 / 1; | |
| 71 | + border-radius: fox.radius(max); | |
| 72 | + } | |
| 73 | + | |
| 74 | + // 체크 글리프. 크기는 슬롯이 정하고 SVG는 따라온다. | |
| 75 | + &__mark > svg { | |
| 76 | + inline-size: fox.number(6); | |
| 77 | + block-size: fox.number(5); | |
| 78 | + } | |
| 79 | + | |
| 80 | + // ongoing의 가운데 점. | |
| 81 | + &__dot { | |
| 82 | + inline-size: fox.number(7); | |
| 83 | + block-size: fox.number(7); | |
| 84 | + flex-shrink: 0; | |
| 85 | + aspect-ratio: 1 / 1; | |
| 86 | + border-radius: fox.radius(max); | |
| 87 | + background: fox.color(element-theme-primary); | |
| 88 | + } | |
| 89 | + | |
| 90 | + &__line { | |
| 91 | + flex-shrink: 0; | |
| 92 | + inline-size: 6.1rem; | |
| 93 | + block-size: 0.15rem; | |
| 94 | + } | |
| 95 | + | |
| 96 | + // ── 글자 ──────────────────────────────────────────────────────────────────── | |
| 97 | + &__text { | |
| 98 | + display: flex; | |
| 99 | + flex-direction: column; | |
| 100 | + align-items: flex-start; | |
| 101 | + align-self: stretch; | |
| 102 | + padding-inline-end: fox.padding(3); | |
| 103 | + | |
| 104 | + // 모바일에서는 동그라미와 선만 남는다(파일 상단 주석 참고). | |
| 105 | + @include fox.mobile { | |
| 106 | + display: none; | |
| 107 | + } | |
| 108 | + } | |
| 109 | + | |
| 110 | + &__step { | |
| 111 | + align-self: stretch; | |
| 112 | + color: fox.color(font-neutral-subtle); | |
| 113 | + font-family: fox.font-family(body); | |
| 114 | + font-size: fox.font-size(body-xsm); | |
| 115 | + font-weight: fox.font-weight(regular); | |
| 116 | + line-height: 1.5; | |
| 117 | + letter-spacing: -0.025em; | |
| 118 | + } | |
| 119 | + | |
| 120 | + &__title { | |
| 121 | + align-self: stretch; | |
| 122 | + color: fox.color(font-neutral-default); | |
| 123 | + font-family: fox.font-family(body); | |
| 124 | + font-size: fox.font-size(body-sm); | |
| 125 | + // 시안의 700은 토큰에 없어 시스템의 bold(800)로 올린다 — 파일 상단 주석 참고. | |
| 126 | + font-weight: fox.font-weight(bold); | |
| 127 | + line-height: 1.5; | |
| 128 | + letter-spacing: -0.025em; | |
| 129 | + } | |
| 130 | + | |
| 131 | + // ── 상태 ──────────────────────────────────────────────────────────────────── | |
| 132 | + // 지난 단계. 동그라미가 회색으로 채워지고 체크가 반전색으로 얹힌다. 선도 함께 진하다 — | |
| 133 | + // 여기까지 왔다는 표시다. | |
| 134 | + &--completion { | |
| 135 | + .fox-step-indicator-item__mark { | |
| 136 | + background: fox.color(element-neutral-default); | |
| 137 | + color: fox.color(element-neutral-inverse-static); | |
| 138 | + } | |
| 139 | + | |
| 140 | + .fox-step-indicator-item__line { | |
| 141 | + background: fox.color(element-neutral-default); | |
| 142 | + } | |
| 143 | + } | |
| 144 | + | |
| 145 | + // 지금 단계. 테두리만 강조색이고 안은 비어 있으며, 가운데 점이 그 자리를 채운다. | |
| 146 | + // 선은 앞으로 갈 길이라 흐리다. | |
| 147 | + &--ongoing { | |
| 148 | + .fox-step-indicator-item__mark { | |
| 149 | + border: 0.15rem solid fox.color(element-theme-primary); | |
| 150 | + } | |
| 151 | + | |
| 152 | + .fox-step-indicator-item__line { | |
| 153 | + background: fox.color(element-neutral-disabled); | |
| 154 | + } | |
| 155 | + } | |
| 156 | + | |
| 157 | + // 아직 오지 않은 단계. 흐린 테두리에 연한 면이고 안은 비었다. | |
| 158 | + &--before { | |
| 159 | + .fox-step-indicator-item__mark { | |
| 160 | + border: fox.border(1) solid fox.color(element-neutral-disabled); | |
| 161 | + background: fox.color(element-neutral-subtle); | |
| 162 | + } | |
| 163 | + | |
| 164 | + .fox-step-indicator-item__line { | |
| 165 | + background: fox.color(element-neutral-disabled); | |
| 166 | + } | |
| 167 | + } | |
| 168 | +} |
+++ @fox/styles/_fox-step-indicator.scss
... | ... | @@ -0,0 +1,34 @@ |
| 1 | +// FoxStepIndicator — 시안: 통합관리자페이지 디자인시스템 Figma step-indicator | |
| 2 | +// | |
| 3 | +// `fox-step-indicator-item`들을 가로로 늘어놓는 묶음이다. 배치만 갖는다 — 어디까지 왔는지는 각 단계가 말한다. | |
| 4 | +// | |
| 5 | +// 마크업 계약 (React 밖 소비자용): | |
| 6 | +// <div class="fox-step-indicator" role="group" aria-label="가입 절차"> | |
| 7 | +// …fox-step-indicator-item (completion)… | |
| 8 | +// …fox-step-indicator-item (ongoing)… | |
| 9 | +// …fox-step-indicator-item (before, 마지막이면 선 없이)… | |
| 10 | +// </div> | |
| 11 | +// | |
| 12 | +// 토큰이 아닌 값: | |
| 13 | +// - ⚠️ `inline-size: 40rem`(400px): 시안 값 그대로다. 80px × 5단계라 **단계가 다섯 개일 때의 | |
| 14 | +// 폭**이고, 개수가 다르면 맞지 않는다. 토큰 스케일에도 400이 없다. 단계 수가 다른 화면은 | |
| 15 | +// 호출부가 `className`으로 덮는다 — 시안에서 폭 규칙을 받으면 그 값으로 바꾼다. | |
| 16 | +// - display / align-items: 구조 규칙. | |
| 17 | +// | |
| 18 | +// 세로 정렬이 `center`라 글자가 있는 화면과 없는 화면(모바일)에서 동그라미 줄이 같은 높이에 | |
| 19 | +// 놓인다. | |
| 20 | + | |
| 21 | +@use "@fox/styles/abstracts" as fox; | |
| 22 | + | |
| 23 | +.fox-step-indicator { | |
| 24 | + box-sizing: border-box; | |
| 25 | + display: flex; | |
| 26 | + align-items: center; | |
| 27 | + inline-size: 40rem; | |
| 28 | + | |
| 29 | + // 모바일에서는 글자가 사라져 폭이 남는다 — 고정 폭을 풀어 화면에 맞춘다 | |
| 30 | + // (`fox-step-indicator-item`의 `__text`가 숨는 것과 짝이다). | |
| 31 | + @include fox.mobile { | |
| 32 | + inline-size: 100%; | |
| 33 | + } | |
| 34 | +} |
--- @fox/styles/components.scss
+++ @fox/styles/components.scss
... | ... | @@ -55,6 +55,8 @@ |
| 55 | 55 |
@use "fox-snackbar-list"; |
| 56 | 56 |
@use "fox-spinner"; |
| 57 | 57 |
@use "fox-status-indicator"; |
| 58 |
+@use "fox-step-indicator-item"; |
|
| 59 |
+@use "fox-step-indicator"; |
|
| 58 | 60 |
@use "fox-toast"; |
| 59 | 61 |
@use "fox-toggle-quantity"; |
| 60 | 62 |
@use "fox-page-header"; |
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?