feat: FoxButton 추가 — 로직·구조와 시안 디자인 적용
Figma 시안(관리자페이지 node 390:439)을 토큰만으로 옮겼다. 컴파일된 CSS의 토큰 참조 50개, 원시 hex/px/rem 리터럴 0개다. 시안에서 확인한 것(추측하지 않음): gap은 lg·md·sm이 gap-2이고 xsm만 gap-1이다. primary에는 테두리가 없고 secondary·default만 border-1을 갖는다. disabled는 계열과 무관하게 테두리가 사라진다(시안에서 disabled-border 미사용). 높이만 고정이고 좌우 padding만 지정돼 너비는 가변이다. 토큰이 아닌 코드 세 가지를 사용자 승인 후 넣었다. - line-height: 1 / letter-spacing: -0.025em — 시안 텍스트 스타일 label/*-strong의 값이나 Figma에서 변수로 export되지 않아 토큰이 없다. 네 크기가 같은 값이라 규칙 하나로 끝난다. - box-sizing: border-box — 높이 고정 + secondary·default의 1px 테두리 때문에 필수다. 없으면 계열별 바깥 높이가 2px 어긋난다. - cursor·white-space·flex-shrink — 구조·상호작용 규칙. 버튼 스코프에만 적용한다. 모양 규칙은 @fox/styles/_fox-button.scss가 소유하고 module이 @use로 끌어올린다 — 클래스명이 모듈 스코프로 감싸져 전역 충돌이 없고, index.scss는 이 파티셜을 포함하지 않아 CSS가 중복 출력되지 않는다. 아이콘은 계열별 색을 토큰으로 입히기 위해 ReactNode로 받는다(currentColor 전제). 크기는 슬롯이 form-icon-* 토큰으로 정한다. error 계열은 시안 미정이라 클래스를 비워 뒀다. Co-Authored-By: Claude Opus 5
@da813ceeac1b27de48b84bf348c3bdd4b65b9c6b
+++ @fox/core/components/index.ts
... | ... | @@ -0,0 +1,1 @@ |
| 1 | +export * from "./fox-button"; |
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 |
import type { ReactNode } from "react";
|
| 2 |
+import { FoxButton } from "../core/components/fox-button";
|
|
| 2 | 3 |
|
| 3 | 4 |
/** |
| 4 | 5 |
* 컴포넌트 예제 하나. 사이드바의 "컴포넌트" 분류에 항목으로 뜬다. |
... | ... | @@ -17,24 +18,89 @@ |
| 17 | 18 |
variants: { label: string; node: ReactNode }[];
|
| 18 | 19 |
} |
| 19 | 20 |
|
| 21 |
+// 예제용 아이콘 — 시안의 실제 아이콘이 아니라 자리 확인용 도형이다. |
|
| 22 |
+// `currentColor`로 그려야 버튼이 계열별로 색을 입힐 수 있다(슬롯이 `color`를 내려준다). |
|
| 23 |
+// 크기는 슬롯이 `form-icon-*` 토큰으로 정하므로 100%로 채우기만 한다. |
|
| 24 |
+function DemoIcon() {
|
|
| 25 |
+ return ( |
|
| 26 |
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> |
|
| 27 |
+ <path d="M6 3l5 5-5 5" /> |
|
| 28 |
+ </svg> |
|
| 29 |
+ ); |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 20 | 32 |
/** |
| 21 | 33 |
* 컴포넌트를 만들 때마다 여기에 한 항목씩 추가한다. 사이드바·본문은 이 배열만 보고 |
| 22 | 34 |
* 그리므로 다른 파일을 고칠 필요가 없다. |
| 23 |
- * |
|
| 24 |
- * ```tsx |
|
| 25 |
- * import { Button } from "../core/components/button";
|
|
| 26 |
- * |
|
| 27 |
- * export const COMPONENT_EXAMPLES: ComponentExample[] = [ |
|
| 28 |
- * {
|
|
| 29 |
- * id: "button", |
|
| 30 |
- * name: "Button", |
|
| 31 |
- * description: "기본 액션 버튼", |
|
| 32 |
- * variants: [ |
|
| 33 |
- * { label: "primary", node: <Button variant="primary">확인</Button> },
|
|
| 34 |
- * { label: "disabled", node: <Button disabled>확인</Button> },
|
|
| 35 |
- * ], |
|
| 36 |
- * }, |
|
| 37 |
- * ]; |
|
| 38 |
- * ``` |
|
| 39 | 35 |
*/ |
| 40 |
-export const COMPONENT_EXAMPLES: ComponentExample[] = []; |
|
| 36 |
+export const COMPONENT_EXAMPLES: ComponentExample[] = [ |
|
| 37 |
+ {
|
|
| 38 |
+ id: "fox-button", |
|
| 39 |
+ name: "FoxButton", |
|
| 40 |
+ description: |
|
| 41 |
+ "Figma 시안(관리자페이지 node 390:439)을 토큰만으로 옮겼습니다. 너비는 내용에 따라 가변이고 높이만 고정입니다. hover·pressed는 마우스를 올리거나 눌러 확인하세요. error 계열은 시안 미정이라 비어 있습니다.", |
|
| 42 |
+ variants: [ |
|
| 43 |
+ {
|
|
| 44 |
+ label: "계열 (type) — hover·pressed 확인 가능", |
|
| 45 |
+ node: ( |
|
| 46 |
+ <> |
|
| 47 |
+ <FoxButton size="md" label="default" /> |
|
| 48 |
+ <FoxButton size="md" type="primary" label="primary" /> |
|
| 49 |
+ <FoxButton size="md" type="secondary" label="secondary" /> |
|
| 50 |
+ <FoxButton size="md" type="error" label="error (시안 미정)" /> |
|
| 51 |
+ </> |
|
| 52 |
+ ), |
|
| 53 |
+ }, |
|
| 54 |
+ {
|
|
| 55 |
+ label: "크기 (size)", |
|
| 56 |
+ node: ( |
|
| 57 |
+ <> |
|
| 58 |
+ <FoxButton size="lg" label="lg" /> |
|
| 59 |
+ <FoxButton size="md" label="md" /> |
|
| 60 |
+ <FoxButton size="sm" label="sm" /> |
|
| 61 |
+ <FoxButton size="xsm" label="xsm" /> |
|
| 62 |
+ </> |
|
| 63 |
+ ), |
|
| 64 |
+ }, |
|
| 65 |
+ {
|
|
| 66 |
+ label: "아이콘", |
|
| 67 |
+ node: ( |
|
| 68 |
+ <> |
|
| 69 |
+ <FoxButton size="md" label="leading" leadingIcon={<DemoIcon />} />
|
|
| 70 |
+ <FoxButton size="md" label="trailing" trailingIcon={<DemoIcon />} />
|
|
| 71 |
+ <FoxButton size="md" label="both" leadingIcon={<DemoIcon />} trailingIcon={<DemoIcon />} />
|
|
| 72 |
+ </> |
|
| 73 |
+ ), |
|
| 74 |
+ }, |
|
| 75 |
+ {
|
|
| 76 |
+ label: "상태 — disabled / loading (둘 다 클릭이 막힌다)", |
|
| 77 |
+ node: ( |
|
| 78 |
+ <> |
|
| 79 |
+ <FoxButton size="md" label="normal" onAction={() => undefined} />
|
|
| 80 |
+ <FoxButton size="md" label="disabled" disabled /> |
|
| 81 |
+ <FoxButton size="md" label="loading" loading /> |
|
| 82 |
+ <FoxButton size="md" label="loading + icon" loading leadingIcon={<DemoIcon />} />
|
|
| 83 |
+ </> |
|
| 84 |
+ ), |
|
| 85 |
+ }, |
|
| 86 |
+ {
|
|
| 87 |
+ label: "fullWidth", |
|
| 88 |
+ node: <FoxButton size="md" label="fullWidth" fullWidth />, |
|
| 89 |
+ }, |
|
| 90 |
+ {
|
|
| 91 |
+ label: "htmlType — 폼 안에서의 역할", |
|
| 92 |
+ node: ( |
|
| 93 |
+ <> |
|
| 94 |
+ <FoxButton size="md" label="button (기본)" /> |
|
| 95 |
+ <FoxButton size="md" label="submit" htmlType="submit" /> |
|
| 96 |
+ <FoxButton size="md" label="reset" htmlType="reset" /> |
|
| 97 |
+ </> |
|
| 98 |
+ ), |
|
| 99 |
+ }, |
|
| 100 |
+ {
|
|
| 101 |
+ label: "hidden — 렌더되지 않는다(아래는 비어 있는 것이 정상)", |
|
| 102 |
+ node: <FoxButton size="md" label="hidden" hidden />, |
|
| 103 |
+ }, |
|
| 104 |
+ ], |
|
| 105 |
+ }, |
|
| 106 |
+]; |
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?