--- @fox/dev-test/dev-test-page.tsx
+++ @fox/dev-test/dev-test-page.tsx
... | ... | @@ -2,7 +2,13 @@ |
| 2 | 2 |
|
| 3 | 3 |
import { useState, useSyncExternalStore } from "react";
|
| 4 | 4 |
import { FoxBadge } from "../core/components/fox-badge";
|
| 5 |
-import { FoxCaretDownIcon, FoxCaretUpIcon, FoxStarIcon } from "../core/icons";
|
|
| 5 |
+import { FoxInput } from "../core/components/fox-input";
|
|
| 6 |
+import {
|
|
| 7 |
+ FoxCaretDownIcon, |
|
| 8 |
+ FoxCaretUpIcon, |
|
| 9 |
+ FoxMagnifyingGlassIcon, |
|
| 10 |
+ FoxStarIcon, |
|
| 11 |
+} from "../core/icons"; |
|
| 6 | 12 |
import { cx } from "../core/utils";
|
| 7 | 13 |
import { COMPONENT_GROUPS } from "./component-groups";
|
| 8 | 14 |
import { COMPONENT_EXAMPLES } from "./component-registry";
|
... | ... | @@ -131,6 +137,39 @@ |
| 131 | 137 |
return ids; |
| 132 | 138 |
} |
| 133 | 139 |
|
| 140 |
+/** |
|
| 141 |
+ * 검색은 라벨과 id를 함께 본다 — 화면에는 `FoxButton`으로 보이지만 `fox-button`으로 찾는 |
|
| 142 |
+ * 사람도 있다. 분류 이름이 맞으면 그 아래는 통째로 남긴다. |
|
| 143 |
+ */ |
|
| 144 |
+function filterTokenGroups(groups: TokenGroup[], hit: (text: string) => boolean): TokenGroup[] {
|
|
| 145 |
+ return groups.flatMap((group) => {
|
|
| 146 |
+ if (group.category && hit(group.category)) {
|
|
| 147 |
+ return [group]; |
|
| 148 |
+ } |
|
| 149 |
+ const sections = group.sections.filter((section) => hit(section.label) || hit(section.id)); |
|
| 150 |
+ return sections.length > 0 ? [{ ...group, sections }] : [];
|
|
| 151 |
+ }); |
|
| 152 |
+} |
|
| 153 |
+ |
|
| 154 |
+function filterComponentGroups( |
|
| 155 |
+ groups: ComponentGroupView[], |
|
| 156 |
+ hit: (text: string) => boolean |
|
| 157 |
+): ComponentGroupView[] {
|
|
| 158 |
+ return groups.flatMap((group) => {
|
|
| 159 |
+ const groupHit = hit(group.label); |
|
| 160 |
+ const subgroups = group.subgroups.flatMap((subgroup) => {
|
|
| 161 |
+ if (groupHit || hit(subgroup.label)) {
|
|
| 162 |
+ return [subgroup]; |
|
| 163 |
+ } |
|
| 164 |
+ const examples = subgroup.examples.filter( |
|
| 165 |
+ (example) => hit(example.name) || hit(example.id) |
|
| 166 |
+ ); |
|
| 167 |
+ return examples.length > 0 ? [{ ...subgroup, examples }] : [];
|
|
| 168 |
+ }); |
|
| 169 |
+ return subgroups.length > 0 ? [{ ...group, subgroups }] : [];
|
|
| 170 |
+ }); |
|
| 171 |
+} |
|
| 172 |
+ |
|
| 134 | 173 |
function CountBadge({ value }: { value: number }) {
|
| 135 | 174 |
return ( |
| 136 | 175 |
<span className={styles.count}>
|
... | ... | @@ -146,6 +185,7 @@ |
| 146 | 185 |
const sections = useCssTokens(); |
| 147 | 186 |
// 사용자가 직접 누른 것만 담는다. 없으면 선택 항목에서 파생된 기본값을 쓴다. |
| 148 | 187 |
const [openOverrides, setOpenOverrides] = useState<Record<string, boolean>>({});
|
| 188 |
+ const [keyword, setKeyword] = useState("");
|
|
| 149 | 189 |
|
| 150 | 190 |
if (!sections) {
|
| 151 | 191 |
return ( |
... | ... | @@ -155,15 +195,32 @@ |
| 155 | 195 |
); |
| 156 | 196 |
} |
| 157 | 197 |
|
| 158 |
- const totalTokens = sections.reduce((sum, section) => sum + section.tokens.length, 0); |
|
| 159 | 198 |
const current = selection ?? { kind: "token", id: sections[0]?.id ?? "" };
|
| 160 | 199 |
const activeSection = sections.find((section) => section.id === current.id); |
| 161 | 200 |
|
| 162 |
- const tokenGroups = groupedSections(sections); |
|
| 163 |
- const componentGroups = groupedComponents(); |
|
| 164 |
- const openByDefault = defaultOpenIds(current, tokenGroups, componentGroups); |
|
| 201 |
+ const query = keyword.trim().toLowerCase(); |
|
| 202 |
+ const hit = (text: string) => text.toLowerCase().includes(query); |
|
| 165 | 203 |
|
| 166 |
- const isOpen = (id: string) => openOverrides[id] ?? openByDefault.has(id); |
|
| 204 |
+ const allTokenGroups = groupedSections(sections); |
|
| 205 |
+ const allComponentGroups = groupedComponents(); |
|
| 206 |
+ const tokenGroups = query ? filterTokenGroups(allTokenGroups, hit) : allTokenGroups; |
|
| 207 |
+ const componentGroups = query |
|
| 208 |
+ ? filterComponentGroups(allComponentGroups, hit) |
|
| 209 |
+ : allComponentGroups; |
|
| 210 |
+ |
|
| 211 |
+ const tokenCount = tokenGroups.reduce( |
|
| 212 |
+ (sum, group) => sum + group.sections.reduce((n, section) => n + section.tokens.length, 0), |
|
| 213 |
+ 0 |
|
| 214 |
+ ); |
|
| 215 |
+ const componentCount = componentGroups.reduce( |
|
| 216 |
+ (sum, group) => sum + group.subgroups.reduce((n, sub) => n + sub.examples.length, 0), |
|
| 217 |
+ 0 |
|
| 218 |
+ ); |
|
| 219 |
+ |
|
| 220 |
+ const openByDefault = defaultOpenIds(current, allTokenGroups, allComponentGroups); |
|
| 221 |
+ |
|
| 222 |
+ // 검색 중에는 접힘을 무시한다 — 걸린 항목이 접힌 가지 안에 숨으면 찾은 의미가 없다. |
|
| 223 |
+ const isOpen = (id: string) => (query ? true : openOverrides[id] ?? openByDefault.has(id)); |
|
| 167 | 224 |
const toggle = (id: string) => |
| 168 | 225 |
setOpenOverrides((prev) => ({ ...prev, [id]: !(prev[id] ?? openByDefault.has(id)) }));
|
| 169 | 226 |
|
... | ... | @@ -174,198 +231,216 @@ |
| 174 | 231 |
<div className={styles.shell}>
|
| 175 | 232 |
<aside className={styles.sidebar}>
|
| 176 | 233 |
<div className={styles.top}>
|
| 177 |
- <div className={styles.brand}>
|
|
| 178 |
- <span className={styles.brandTitle}>@fox 디자인 시스템</span>
|
|
| 179 |
- <span className={styles.brandNote}>개발 전용 테스트 페이지</span>
|
|
| 234 |
+ <div className={styles.search}>
|
|
| 235 |
+ <FoxInput |
|
| 236 |
+ size="md" |
|
| 237 |
+ value={keyword}
|
|
| 238 |
+ onChange={setKeyword}
|
|
| 239 |
+ placeholder="검색어를 입력하세요." |
|
| 240 |
+ icon={<FoxMagnifyingGlassIcon />}
|
|
| 241 |
+ clearable |
|
| 242 |
+ clearLabel="검색어 지우기" |
|
| 243 |
+ aria-label="메뉴 검색" |
|
| 244 |
+ /> |
|
| 180 | 245 |
</div> |
| 181 | 246 |
|
| 182 |
- <ThemeSwitch /> |
|
| 247 |
+ <div className={styles.themeRow}>
|
|
| 248 |
+ <ThemeSwitch /> |
|
| 249 |
+ </div> |
|
| 183 | 250 |
</div> |
| 184 | 251 |
|
| 185 | 252 |
<nav aria-label="섹션"> |
| 186 |
- <section className={styles.section}>
|
|
| 187 |
- <h2 className={styles.sectionTitle}>토큰 · {totalTokens}</h2>
|
|
| 253 |
+ {tokenGroups.length === 0 && componentGroups.length === 0 && (
|
|
| 254 |
+ <p className={styles.empty}>검색 결과가 없습니다.</p>
|
|
| 255 |
+ )} |
|
| 188 | 256 |
|
| 189 |
- <ul className={styles.list}>
|
|
| 190 |
- {tokenGroups.map((group) => {
|
|
| 191 |
- // 상위 분류가 없는 섹션은 1depth에서 바로 고른다. |
|
| 192 |
- if (!group.category) {
|
|
| 193 |
- return group.sections.map((section) => ( |
|
| 194 |
- <li key={section.id} className={styles.item}>
|
|
| 257 |
+ {tokenGroups.length > 0 && (
|
|
| 258 |
+ <section className={styles.section}>
|
|
| 259 |
+ <h2 className={styles.sectionTitle}>토큰 · {tokenCount}</h2>
|
|
| 260 |
+ |
|
| 261 |
+ <ul className={styles.list}>
|
|
| 262 |
+ {tokenGroups.map((group) => {
|
|
| 263 |
+ // 상위 분류가 없는 섹션은 1depth에서 바로 고른다. |
|
| 264 |
+ if (!group.category) {
|
|
| 265 |
+ return group.sections.map((section) => ( |
|
| 266 |
+ <li key={section.id} className={styles.item}>
|
|
| 267 |
+ <button |
|
| 268 |
+ type="button" |
|
| 269 |
+ className={cx(
|
|
| 270 |
+ styles.itemButton, |
|
| 271 |
+ isTokenActive(section.id) && styles.itemButtonActive |
|
| 272 |
+ )} |
|
| 273 |
+ aria-current={isTokenActive(section.id) ? "page" : undefined}
|
|
| 274 |
+ onClick={() => select("token", section.id)}
|
|
| 275 |
+ > |
|
| 276 |
+ <span className={styles.itemLabel}>{section.label}</span>
|
|
| 277 |
+ <CountBadge value={section.tokens.length} />
|
|
| 278 |
+ </button> |
|
| 279 |
+ </li> |
|
| 280 |
+ )); |
|
| 281 |
+ } |
|
| 282 |
+ |
|
| 283 |
+ const groupId = tokenGroupId(group.category); |
|
| 284 |
+ const open = isOpen(groupId); |
|
| 285 |
+ const containsActive = group.sections.some((section) => |
|
| 286 |
+ isTokenActive(section.id) |
|
| 287 |
+ ); |
|
| 288 |
+ |
|
| 289 |
+ return ( |
|
| 290 |
+ <li key={groupId} className={styles.item}>
|
|
| 195 | 291 |
<button |
| 196 | 292 |
type="button" |
| 197 | 293 |
className={cx(
|
| 198 | 294 |
styles.itemButton, |
| 199 |
- isTokenActive(section.id) && styles.itemButtonActive |
|
| 295 |
+ containsActive && styles.itemButtonActive |
|
| 200 | 296 |
)} |
| 201 |
- aria-current={isTokenActive(section.id) ? "page" : undefined}
|
|
| 202 |
- onClick={() => select("token", section.id)}
|
|
| 297 |
+ aria-expanded={open}
|
|
| 298 |
+ onClick={() => toggle(groupId)}
|
|
| 203 | 299 |
> |
| 204 |
- <span className={styles.itemLabel}>{section.label}</span>
|
|
| 205 |
- <CountBadge value={section.tokens.length} />
|
|
| 300 |
+ <span className={styles.itemLabel}>{group.category}</span>
|
|
| 206 | 301 |
</button> |
| 207 |
- </li> |
|
| 208 |
- )); |
|
| 209 |
- } |
|
| 210 | 302 |
|
| 211 |
- const groupId = tokenGroupId(group.category); |
|
| 212 |
- const open = isOpen(groupId); |
|
| 213 |
- const containsActive = group.sections.some((section) => |
|
| 214 |
- isTokenActive(section.id) |
|
| 215 |
- ); |
|
| 216 |
- |
|
| 217 |
- return ( |
|
| 218 |
- <li key={groupId} className={styles.item}>
|
|
| 219 |
- <button |
|
| 220 |
- type="button" |
|
| 221 |
- className={cx(
|
|
| 222 |
- styles.itemButton, |
|
| 223 |
- containsActive && styles.itemButtonActive |
|
| 224 |
- )} |
|
| 225 |
- aria-expanded={open}
|
|
| 226 |
- onClick={() => toggle(groupId)}
|
|
| 227 |
- > |
|
| 228 |
- <span className={styles.itemLabel}>{group.category}</span>
|
|
| 229 |
- </button> |
|
| 230 |
- |
|
| 231 |
- {open && (
|
|
| 232 |
- <ul className={styles.childGroup}>
|
|
| 233 |
- {group.sections.map((section) => (
|
|
| 234 |
- <li key={section.id} className={styles.child}>
|
|
| 235 |
- <button |
|
| 236 |
- type="button" |
|
| 237 |
- className={cx(
|
|
| 238 |
- styles.childRow, |
|
| 239 |
- isTokenActive(section.id) && styles.childRowActive |
|
| 240 |
- )} |
|
| 241 |
- aria-current={isTokenActive(section.id) ? "page" : undefined}
|
|
| 242 |
- onClick={() => select("token", section.id)}
|
|
| 243 |
- > |
|
| 244 |
- <span className={cx(styles.childMark, styles.childMarkHidden)}>
|
|
| 245 |
- <FoxStarIcon /> |
|
| 246 |
- </span> |
|
| 247 |
- <span className={styles.childLabel}>{section.label}</span>
|
|
| 248 |
- <CountBadge value={section.tokens.length} />
|
|
| 249 |
- </button> |
|
| 250 |
- </li> |
|
| 251 |
- ))} |
|
| 252 |
- </ul> |
|
| 253 |
- )} |
|
| 254 |
- </li> |
|
| 255 |
- ); |
|
| 256 |
- })} |
|
| 257 |
- </ul> |
|
| 258 |
- </section> |
|
| 259 |
- |
|
| 260 |
- <section className={styles.section}>
|
|
| 261 |
- <h2 className={styles.sectionTitle}>컴포넌트 · {COMPONENT_EXAMPLES.length}</h2>
|
|
| 262 |
- |
|
| 263 |
- <ul className={styles.list}>
|
|
| 264 |
- {componentGroups.map((group) => {
|
|
| 265 |
- const groupId = componentGroupId(group.label); |
|
| 266 |
- const open = isOpen(groupId); |
|
| 267 |
- const containsActive = group.subgroups.some((subgroup) => |
|
| 268 |
- subgroup.examples.some((example) => isComponentActive(example.id)) |
|
| 269 |
- ); |
|
| 270 |
- |
|
| 271 |
- return ( |
|
| 272 |
- <li key={group.label} className={styles.item}>
|
|
| 273 |
- <button |
|
| 274 |
- type="button" |
|
| 275 |
- className={cx(
|
|
| 276 |
- styles.itemButton, |
|
| 277 |
- containsActive && styles.itemButtonActive |
|
| 278 |
- )} |
|
| 279 |
- aria-expanded={open}
|
|
| 280 |
- onClick={() => toggle(groupId)}
|
|
| 281 |
- > |
|
| 282 |
- <span className={styles.itemLabel}>{group.label}</span>
|
|
| 283 |
- </button> |
|
| 284 |
- |
|
| 285 |
- {open && (
|
|
| 286 |
- <ul className={styles.childGroup}>
|
|
| 287 |
- {group.subgroups.map((subgroup) => {
|
|
| 288 |
- const subgroupId = `${groupId}/${subgroup.label}`;
|
|
| 289 |
- const subgroupOpen = isOpen(subgroupId); |
|
| 290 |
- |
|
| 291 |
- return ( |
|
| 292 |
- <li key={subgroup.label} className={styles.child}>
|
|
| 303 |
+ {open && (
|
|
| 304 |
+ <ul className={styles.childGroup}>
|
|
| 305 |
+ {group.sections.map((section) => (
|
|
| 306 |
+ <li key={section.id} className={styles.child}>
|
|
| 293 | 307 |
<button |
| 294 | 308 |
type="button" |
| 295 | 309 |
className={cx(
|
| 296 | 310 |
styles.childRow, |
| 297 |
- subgroupOpen && styles.childRowActive |
|
| 311 |
+ isTokenActive(section.id) && styles.childRowActive |
|
| 298 | 312 |
)} |
| 299 |
- aria-expanded={subgroupOpen}
|
|
| 300 |
- onClick={() => toggle(subgroupId)}
|
|
| 313 |
+ aria-current={isTokenActive(section.id) ? "page" : undefined}
|
|
| 314 |
+ onClick={() => select("token", section.id)}
|
|
| 301 | 315 |
> |
| 302 |
- <span className={styles.childMark}>
|
|
| 316 |
+ <span className={cx(styles.childMark, styles.childMarkHidden)}>
|
|
| 303 | 317 |
<FoxStarIcon /> |
| 304 | 318 |
</span> |
| 305 |
- <span className={styles.childLabel}>{subgroup.label}</span>
|
|
| 306 |
- <span className={styles.childMark}>
|
|
| 307 |
- {subgroupOpen ? <FoxCaretUpIcon /> : <FoxCaretDownIcon />}
|
|
| 308 |
- </span> |
|
| 319 |
+ <span className={styles.childLabel}>{section.label}</span>
|
|
| 320 |
+ <CountBadge value={section.tokens.length} />
|
|
| 309 | 321 |
</button> |
| 310 |
- |
|
| 311 |
- {subgroupOpen && (
|
|
| 312 |
- <ul className={styles.leafGroup}>
|
|
| 313 |
- {subgroup.examples.map((example) => (
|
|
| 314 |
- <li key={example.id} className={styles.child}>
|
|
| 315 |
- <button |
|
| 316 |
- type="button" |
|
| 317 |
- className={cx(
|
|
| 318 |
- styles.leaf, |
|
| 319 |
- isComponentActive(example.id) && styles.leafActive |
|
| 320 |
- )} |
|
| 321 |
- aria-current={
|
|
| 322 |
- isComponentActive(example.id) ? "page" : undefined |
|
| 323 |
- } |
|
| 324 |
- onClick={() => select("component", example.id)}
|
|
| 325 |
- > |
|
| 326 |
- <span className={styles.childMark}>
|
|
| 327 |
- <FoxStarIcon /> |
|
| 328 |
- </span> |
|
| 329 |
- <span className={styles.childLabel}>{example.name}</span>
|
|
| 330 |
- <CountBadge value={example.variants.length} />
|
|
| 331 |
- </button> |
|
| 332 |
- </li> |
|
| 333 |
- ))} |
|
| 334 |
- </ul> |
|
| 335 |
- )} |
|
| 336 | 322 |
</li> |
| 337 |
- ); |
|
| 338 |
- })} |
|
| 339 |
- </ul> |
|
| 340 |
- )} |
|
| 341 |
- </li> |
|
| 342 |
- ); |
|
| 343 |
- })} |
|
| 323 |
+ ))} |
|
| 324 |
+ </ul> |
|
| 325 |
+ )} |
|
| 326 |
+ </li> |
|
| 327 |
+ ); |
|
| 328 |
+ })} |
|
| 329 |
+ </ul> |
|
| 330 |
+ </section> |
|
| 331 |
+ )} |
|
| 344 | 332 |
|
| 345 |
- {/* 컴포넌트가 아니라 안내라 항상 맨 아래에 두고 생김새를 달리한다. */}
|
|
| 346 |
- <li className={styles.item}>
|
|
| 347 |
- <button |
|
| 348 |
- type="button" |
|
| 349 |
- className={cx(
|
|
| 350 |
- styles.itemButton, |
|
| 351 |
- styles.guideButton, |
|
| 352 |
- current.kind === "component" && |
|
| 353 |
- !isRegistered(current.id) && |
|
| 354 |
- styles.guideButtonActive |
|
| 355 |
- )} |
|
| 356 |
- aria-current={
|
|
| 357 |
- current.kind === "component" && !isRegistered(current.id) ? "page" : undefined |
|
| 358 |
- } |
|
| 359 |
- onClick={() => select("component", GUIDE_ID)}
|
|
| 360 |
- > |
|
| 361 |
- <span className={styles.itemLabel}>추가 방법</span>
|
|
| 362 |
- <span aria-hidden="true" className={styles.guideMark}>
|
|
| 363 |
- + |
|
| 364 |
- </span> |
|
| 365 |
- </button> |
|
| 366 |
- </li> |
|
| 367 |
- </ul> |
|
| 368 |
- </section> |
|
| 333 |
+ {componentGroups.length > 0 && (
|
|
| 334 |
+ <section className={styles.section}>
|
|
| 335 |
+ <h2 className={styles.sectionTitle}>컴포넌트 · {componentCount}</h2>
|
|
| 336 |
+ |
|
| 337 |
+ <ul className={styles.list}>
|
|
| 338 |
+ {componentGroups.map((group) => {
|
|
| 339 |
+ const groupId = componentGroupId(group.label); |
|
| 340 |
+ const open = isOpen(groupId); |
|
| 341 |
+ const containsActive = group.subgroups.some((subgroup) => |
|
| 342 |
+ subgroup.examples.some((example) => isComponentActive(example.id)) |
|
| 343 |
+ ); |
|
| 344 |
+ |
|
| 345 |
+ return ( |
|
| 346 |
+ <li key={group.label} className={styles.item}>
|
|
| 347 |
+ <button |
|
| 348 |
+ type="button" |
|
| 349 |
+ className={cx(
|
|
| 350 |
+ styles.itemButton, |
|
| 351 |
+ containsActive && styles.itemButtonActive |
|
| 352 |
+ )} |
|
| 353 |
+ aria-expanded={open}
|
|
| 354 |
+ onClick={() => toggle(groupId)}
|
|
| 355 |
+ > |
|
| 356 |
+ <span className={styles.itemLabel}>{group.label}</span>
|
|
| 357 |
+ </button> |
|
| 358 |
+ |
|
| 359 |
+ {open && (
|
|
| 360 |
+ <ul className={styles.childGroup}>
|
|
| 361 |
+ {group.subgroups.map((subgroup) => {
|
|
| 362 |
+ const subgroupId = `${groupId}/${subgroup.label}`;
|
|
| 363 |
+ const subgroupOpen = isOpen(subgroupId); |
|
| 364 |
+ |
|
| 365 |
+ return ( |
|
| 366 |
+ <li key={subgroup.label} className={styles.child}>
|
|
| 367 |
+ <button |
|
| 368 |
+ type="button" |
|
| 369 |
+ className={cx(
|
|
| 370 |
+ styles.childRow, |
|
| 371 |
+ subgroupOpen && styles.childRowActive |
|
| 372 |
+ )} |
|
| 373 |
+ aria-expanded={subgroupOpen}
|
|
| 374 |
+ onClick={() => toggle(subgroupId)}
|
|
| 375 |
+ > |
|
| 376 |
+ <span className={styles.childMark}>
|
|
| 377 |
+ <FoxStarIcon /> |
|
| 378 |
+ </span> |
|
| 379 |
+ <span className={styles.childLabel}>{subgroup.label}</span>
|
|
| 380 |
+ <span className={styles.childMark}>
|
|
| 381 |
+ {subgroupOpen ? <FoxCaretUpIcon /> : <FoxCaretDownIcon />}
|
|
| 382 |
+ </span> |
|
| 383 |
+ </button> |
|
| 384 |
+ |
|
| 385 |
+ {subgroupOpen && (
|
|
| 386 |
+ <ul className={styles.leafGroup}>
|
|
| 387 |
+ {subgroup.examples.map((example) => (
|
|
| 388 |
+ <li key={example.id} className={styles.child}>
|
|
| 389 |
+ <button |
|
| 390 |
+ type="button" |
|
| 391 |
+ className={cx(
|
|
| 392 |
+ styles.leaf, |
|
| 393 |
+ isComponentActive(example.id) && styles.leafActive |
|
| 394 |
+ )} |
|
| 395 |
+ aria-current={
|
|
| 396 |
+ isComponentActive(example.id) ? "page" : undefined |
|
| 397 |
+ } |
|
| 398 |
+ onClick={() => select("component", example.id)}
|
|
| 399 |
+ > |
|
| 400 |
+ <span className={styles.childMark}>
|
|
| 401 |
+ <FoxStarIcon /> |
|
| 402 |
+ </span> |
|
| 403 |
+ <span className={styles.childLabel}>{example.name}</span>
|
|
| 404 |
+ <CountBadge value={example.variants.length} />
|
|
| 405 |
+ </button> |
|
| 406 |
+ </li> |
|
| 407 |
+ ))} |
|
| 408 |
+ </ul> |
|
| 409 |
+ )} |
|
| 410 |
+ </li> |
|
| 411 |
+ ); |
|
| 412 |
+ })} |
|
| 413 |
+ </ul> |
|
| 414 |
+ )} |
|
| 415 |
+ </li> |
|
| 416 |
+ ); |
|
| 417 |
+ })} |
|
| 418 |
+ |
|
| 419 |
+ {/* 컴포넌트가 아니라 안내라 항상 맨 아래에 두고 생김새를 달리한다. */}
|
|
| 420 |
+ <li className={styles.item}>
|
|
| 421 |
+ <button |
|
| 422 |
+ type="button" |
|
| 423 |
+ className={cx(
|
|
| 424 |
+ styles.itemButton, |
|
| 425 |
+ styles.guideButton, |
|
| 426 |
+ current.kind === "component" && |
|
| 427 |
+ !isRegistered(current.id) && |
|
| 428 |
+ styles.guideButtonActive |
|
| 429 |
+ )} |
|
| 430 |
+ aria-current={
|
|
| 431 |
+ current.kind === "component" && !isRegistered(current.id) ? "page" : undefined |
|
| 432 |
+ } |
|
| 433 |
+ onClick={() => select("component", GUIDE_ID)}
|
|
| 434 |
+ > |
|
| 435 |
+ <span className={styles.itemLabel}>추가 방법</span>
|
|
| 436 |
+ <span aria-hidden="true" className={styles.guideMark}>
|
|
| 437 |
+ + |
|
| 438 |
+ </span> |
|
| 439 |
+ </button> |
|
| 440 |
+ </li> |
|
| 441 |
+ </ul> |
|
| 442 |
+ </section> |
|
| 443 |
+ )} |
|
| 369 | 444 |
</nav> |
| 370 | 445 |
</aside> |
| 371 | 446 |
|
--- @fox/dev-test/dev-test.module.scss
+++ @fox/dev-test/dev-test.module.scss
... | ... | @@ -54,31 +54,26 @@ |
| 54 | 54 |
.top {
|
| 55 | 55 |
display: flex; |
| 56 | 56 |
flex-direction: column; |
| 57 |
- // 세그먼티드 컨트롤은 inline-flex라 내용만큼만 넓다 — stretch로 늘리면 마지막 칸 뒤에 |
|
| 58 |
- // 빈 자리가 붙어 그 칸이 길어 보인다. |
|
| 59 |
- align-items: flex-start; |
|
| 60 | 57 |
gap: fox.gap(3); |
| 61 | 58 |
padding-block-end: fox.spacing(bottom-sm); |
| 62 | 59 |
border-block-end: fox.border(1) solid fox.color(border-neutral-subtle); |
| 63 | 60 |
} |
| 64 | 61 |
|
| 65 |
-.brand {
|
|
| 62 |
+// FoxInput의 기본 테두리는 input-border(진한 회색)인데 시안 검색창은 border-neutral-subtler다. |
|
| 63 |
+.search :global(.fox-input__box) {
|
|
| 64 |
+ border-color: fox.color(border-neutral-subtler); |
|
| 65 |
+} |
|
| 66 |
+ |
|
| 67 |
+// 세그먼티드 컨트롤은 inline-flex라 내용만큼만 넓다 — 늘리지 않고 줄 가운데에 세운다. |
|
| 68 |
+.themeRow {
|
|
| 66 | 69 |
display: flex; |
| 67 |
- flex-direction: column; |
|
| 68 |
- gap: fox.gap(1); |
|
| 70 |
+ justify-content: center; |
|
| 69 | 71 |
} |
| 70 | 72 |
|
| 71 |
-.brandTitle {
|
|
| 72 |
- color: fox.color(font-neutral-default); |
|
| 73 |
- font-family: fox.font-family(title); |
|
| 74 |
- font-size: fox.font-size(heading-sm); |
|
| 75 |
- font-weight: fox.font-weight(bold); |
|
| 76 |
- letter-spacing: -0.025em; |
|
| 77 |
-} |
|
| 78 |
- |
|
| 79 |
-.brandNote {
|
|
| 73 |
+.empty {
|
|
| 74 |
+ padding-block: fox.spacing(top-md); |
|
| 80 | 75 |
color: fox.color(font-neutral-subtler); |
| 81 |
- font-size: fox.font-size(label-xsm); |
|
| 76 |
+ font-size: fox.font-size(label-sm); |
|
| 82 | 77 |
} |
| 83 | 78 |
|
| 84 | 79 |
// ── 섹션 ──────────────────────────────────────────────────────────────────── |
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?