feat: 체크박스·라디오에 hideLabel 추가
표의 선택 열처럼 네모(동그라미)만 필요한 자리가 있는데 글자를 감출 방법이 없었다. 표 예시에서는 카탈로그 전용 CSS로 가려 두었던 것을 컴포넌트가 갖게 한다. `display: none`으로 지우지 않는다 — 그러면 접근성 트리에서도 빠져 이름 없는 조작기가 된다. 자리만 없애고 DOM에는 남긴다. 루트가 `
@e04e73e0747ea28a1176a82975255c593062fae5
--- @fox/core/components/fox-checkbox/fox-checkbox.tsx
+++ @fox/core/components/fox-checkbox/fox-checkbox.tsx
... | ... | @@ -39,6 +39,11 @@ |
| 39 | 39 |
* 걷어내고 그 자리를 대신한다(`FoxInput`과 같은 방식). |
| 40 | 40 |
*/ |
| 41 | 41 |
onChange?: (checked: boolean, event: ChangeEvent<HTMLInputElement>) => void; |
| 42 |
+ /** |
|
| 43 |
+ * 글자를 눈에서만 감춘다 — 표의 선택 열처럼 네모(동그라미)만 필요한 자리에 쓴다. |
|
| 44 |
+ * DOM에는 남으므로 스크린리더는 그대로 읽는다. `description`도 함께 감춰진다. |
|
| 45 |
+ */ |
|
| 46 |
+ hideLabel?: boolean; |
|
| 42 | 47 |
/** 참이면 렌더하지 않는다(DOM에 남지 않는다). */ |
| 43 | 48 |
hidden?: boolean; |
| 44 | 49 |
/** 배치 조정용. */ |
... | ... | @@ -68,6 +73,7 @@ |
| 68 | 73 |
description, |
| 69 | 74 |
indeterminate = false, |
| 70 | 75 |
onChange, |
| 76 |
+ hideLabel = false, |
|
| 71 | 77 |
hidden = false, |
| 72 | 78 |
className, |
| 73 | 79 |
ref, |
... | ... | @@ -103,7 +109,7 @@ |
| 103 | 109 |
}; |
| 104 | 110 |
|
| 105 | 111 |
return ( |
| 106 |
- <label className={cx("fox-checkbox", SIZE_CLASS[size], className)}>
|
|
| 112 |
+ <label className={cx("fox-checkbox", SIZE_CLASS[size], hideLabel && "fox-checkbox--hide-label", className)}>
|
|
| 107 | 113 |
{/* 형제 선택자(`~`)가 뒤따르는 표시와 본문에 닿아야 하므로 맨 앞에 둔다. */}
|
| 108 | 114 |
<input |
| 109 | 115 |
{...rest}
|
--- @fox/core/components/fox-radio/fox-radio.tsx
+++ @fox/core/components/fox-radio/fox-radio.tsx
... | ... | @@ -25,6 +25,11 @@ |
| 25 | 25 |
* 그 자리를 대신한다(`FoxInput`과 같은 방식). |
| 26 | 26 |
*/ |
| 27 | 27 |
onChange?: (value: string, event: ChangeEvent<HTMLInputElement>) => void; |
| 28 |
+ /** |
|
| 29 |
+ * 글자를 눈에서만 감춘다 — 표의 선택 열처럼 네모(동그라미)만 필요한 자리에 쓴다. |
|
| 30 |
+ * DOM에는 남으므로 스크린리더는 그대로 읽는다. `description`도 함께 감춰진다. |
|
| 31 |
+ */ |
|
| 32 |
+ hideLabel?: boolean; |
|
| 28 | 33 |
/** 참이면 렌더하지 않는다(DOM에 남지 않는다). */ |
| 29 | 34 |
hidden?: boolean; |
| 30 | 35 |
/** 배치 조정용. */ |
... | ... | @@ -54,6 +59,7 @@ |
| 54 | 59 |
label, |
| 55 | 60 |
description, |
| 56 | 61 |
onChange, |
| 62 |
+ hideLabel = false, |
|
| 57 | 63 |
hidden = false, |
| 58 | 64 |
className, |
| 59 | 65 |
...rest |
... | ... | @@ -71,7 +77,7 @@ |
| 71 | 77 |
}; |
| 72 | 78 |
|
| 73 | 79 |
return ( |
| 74 |
- <label className={cx("fox-radio", SIZE_CLASS[size], className)}>
|
|
| 80 |
+ <label className={cx("fox-radio", SIZE_CLASS[size], hideLabel && "fox-radio--hide-label", className)}>
|
|
| 75 | 81 |
{/* 형제 선택자(`~`)가 뒤따르는 표시와 본문에 닿아야 하므로 맨 앞에 둔다. */}
|
| 76 | 82 |
<input {...rest} type="radio" className="fox-radio__input" onChange={handleChange} />
|
| 77 | 83 |
<FoxRadioIcon size={size} />
|
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
... | ... | @@ -490,7 +490,7 @@ |
| 490 | 490 |
<FoxTableCell width={64}>
|
| 491 | 491 |
<FoxCheckbox |
| 492 | 492 |
size="md" |
| 493 |
- className={styles.checkboxOnly}
|
|
| 493 |
+ hideLabel |
|
| 494 | 494 |
label="전체 선택" |
| 495 | 495 |
checked={allOn}
|
| 496 | 496 |
indeterminate={selected.length > 0 && !allOn}
|
... | ... | @@ -514,7 +514,7 @@ |
| 514 | 514 |
<FoxTableCell label="선택"> |
| 515 | 515 |
<FoxCheckbox |
| 516 | 516 |
size="md" |
| 517 |
- className={styles.checkboxOnly}
|
|
| 517 |
+ hideLabel |
|
| 518 | 518 |
label={`${n}번째 줄 선택`}
|
| 519 | 519 |
checked={selected.includes(n)}
|
| 520 | 520 |
onChange={() => toggle(n)}
|
... | ... | @@ -1312,6 +1312,16 @@ |
| 1312 | 1312 |
"Figma 시안(radio 180:278) 동그란 표시 + 라벨 + 부가설명을 묶은 라디오 한 칸입니다.", |
| 1313 | 1313 |
variants: [ |
| 1314 | 1314 |
{
|
| 1315 |
+ label: "글자 감추기 (hideLabel) — 표의 선택 열처럼 상자만 필요할 때", |
|
| 1316 |
+ node: ( |
|
| 1317 |
+ <div className={styles.demoRow}>
|
|
| 1318 |
+ <FoxRadio hideLabel label="라디오 선택" /> |
|
| 1319 |
+ <FoxRadio hideLabel label="라디오 선택 (켜짐)" defaultChecked /> |
|
| 1320 |
+ <FoxRadio label="비교용 — 글자가 보입니다" /> |
|
| 1321 |
+ </div> |
|
| 1322 |
+ ), |
|
| 1323 |
+ }, |
|
| 1324 |
+ {
|
|
| 1315 | 1325 |
label: "size-lg", |
| 1316 | 1326 |
node: ( |
| 1317 | 1327 |
<div className={styles.demoStack}>
|
... | ... | @@ -1928,6 +1938,16 @@ |
| 1928 | 1938 |
"Figma 시안(checkbox 108:469) 네모 표시 + 라벨 + 부가설명을 묶은 체크박스 한 칸입니다. 루트가 <label>이라 라벨이나 설명을 눌러도 켜집니다. 라디오와 달리 name이 같아도 서로를 끄지 않아 여러 개를 동시에 고를 수 있고, 폼 전송 시 고른 것들이 함께 나갑니다. ⚠️ 부분선택은 HTML 속성이 아니라 DOM 프로퍼티라 마크업만으로는 켤 수 없습니다 — 컴포넌트가 대신 넣어 줍니다.", |
| 1929 | 1939 |
variants: [ |
| 1930 | 1940 |
{
|
| 1941 |
+ label: "글자 감추기 (hideLabel) — 표의 선택 열처럼 상자만 필요할 때", |
|
| 1942 |
+ node: ( |
|
| 1943 |
+ <div className={styles.demoRow}>
|
|
| 1944 |
+ <FoxCheckbox hideLabel label="체크박스 선택" /> |
|
| 1945 |
+ <FoxCheckbox hideLabel label="체크박스 선택 (켜짐)" defaultChecked /> |
|
| 1946 |
+ <FoxCheckbox label="비교용 — 글자가 보입니다" /> |
|
| 1947 |
+ </div> |
|
| 1948 |
+ ), |
|
| 1949 |
+ }, |
|
| 1950 |
+ {
|
|
| 1931 | 1951 |
label: "여러 개를 동시에 고를 수 있다 (같은 name이어도 서로 끄지 않는다)", |
| 1932 | 1952 |
node: ( |
| 1933 | 1953 |
<div className={styles.demoStack}>
|
--- @fox/dev-test/dev-test.module.scss
+++ @fox/dev-test/dev-test.module.scss
... | ... | @@ -510,13 +510,3 @@ |
| 510 | 510 |
color: fox.color(icon-neutral-default); |
| 511 | 511 |
} |
| 512 | 512 |
} |
| 513 |
- |
|
| 514 |
-// 표의 체크 열처럼 상자만 필요한 자리. FoxCheckbox에 라벨을 감추는 프롭이 없어 |
|
| 515 |
-// 카탈로그에서만 이렇게 가린다 — 이름은 남으므로 스크린리더는 그대로 읽는다. |
|
| 516 |
-.checkboxOnly :global(.fox-checkbox__body) {
|
|
| 517 |
- position: absolute; |
|
| 518 |
- inline-size: fox.border(1); |
|
| 519 |
- block-size: fox.border(1); |
|
| 520 |
- overflow: hidden; |
|
| 521 |
- clip-path: inset(50%); |
|
| 522 |
-} |
--- @fox/styles/_fox-checkbox.scss
+++ @fox/styles/_fox-checkbox.scss
... | ... | @@ -121,3 +121,18 @@ |
| 121 | 121 |
.fox-checkbox__input:disabled ~ .fox-checkbox__body .fox-checkbox__description {
|
| 122 | 122 |
color: fox.color(font-neutral-disabled); |
| 123 | 123 |
} |
| 124 |
+ |
|
| 125 |
+// ── 글자 감추기 ─────────────────────────────────────────────────────────────── |
|
| 126 |
+// `display: none`으로 지우면 접근성 트리에서도 빠져 이름 없는 조작기가 된다. 그래서 |
|
| 127 |
+// 자리만 없애고 DOM에는 남긴다 — 루트가 `<label>`이라 이 글자가 곧 이름이다. |
|
| 128 |
+.fox-checkbox--hide-label {
|
|
| 129 |
+ gap: 0; |
|
| 130 |
+ |
|
| 131 |
+ > .fox-checkbox__body {
|
|
| 132 |
+ position: absolute; |
|
| 133 |
+ inline-size: fox.border(1); |
|
| 134 |
+ block-size: fox.border(1); |
|
| 135 |
+ overflow: hidden; |
|
| 136 |
+ clip-path: inset(50%); |
|
| 137 |
+ } |
|
| 138 |
+} |
--- @fox/styles/_fox-radio.scss
+++ @fox/styles/_fox-radio.scss
... | ... | @@ -113,3 +113,18 @@ |
| 113 | 113 |
.fox-radio__input:disabled ~ .fox-radio__body .fox-radio__description {
|
| 114 | 114 |
color: fox.color(font-neutral-disabled); |
| 115 | 115 |
} |
| 116 |
+ |
|
| 117 |
+// ── 글자 감추기 ─────────────────────────────────────────────────────────────── |
|
| 118 |
+// `display: none`으로 지우면 접근성 트리에서도 빠져 이름 없는 조작기가 된다. 그래서 |
|
| 119 |
+// 자리만 없애고 DOM에는 남긴다 — 루트가 `<label>`이라 이 글자가 곧 이름이다. |
|
| 120 |
+.fox-radio--hide-label {
|
|
| 121 |
+ gap: 0; |
|
| 122 |
+ |
|
| 123 |
+ > .fox-radio__body {
|
|
| 124 |
+ position: absolute; |
|
| 125 |
+ inline-size: fox.border(1); |
|
| 126 |
+ block-size: fox.border(1); |
|
| 127 |
+ overflow: hidden; |
|
| 128 |
+ clip-path: inset(50%); |
|
| 129 |
+ } |
|
| 130 |
+} |
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?