@hjmds/design-contracts 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agreement.d.ts +183 -0
- package/dist/agreement.d.ts.map +1 -0
- package/dist/agreement.js +137 -0
- package/dist/agreement.js.map +1 -0
- package/dist/anchor.d.ts +62 -0
- package/dist/anchor.d.ts.map +1 -0
- package/dist/anchor.js +43 -0
- package/dist/anchor.js.map +1 -0
- package/dist/asset.d.ts +94 -0
- package/dist/asset.d.ts.map +1 -0
- package/dist/asset.js +60 -0
- package/dist/asset.js.map +1 -0
- package/dist/base-recipes.d.ts +19 -0
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +17 -0
- package/dist/base-recipes.js.map +1 -1
- package/dist/behaviors.d.ts +346 -3
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/behaviors.js +35 -1
- package/dist/behaviors.js.map +1 -1
- package/dist/bottom-info.d.ts +58 -0
- package/dist/bottom-info.d.ts.map +1 -0
- package/dist/bottom-info.js +48 -0
- package/dist/bottom-info.js.map +1 -0
- package/dist/carousel.d.ts +3 -3
- package/dist/carousel.js +1 -1
- package/dist/carousel.js.map +1 -1
- package/dist/catalog.d.ts +1250 -239
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +65 -25
- package/dist/catalog.js.map +1 -1
- package/dist/collapsible.d.ts +56 -0
- package/dist/collapsible.d.ts.map +1 -0
- package/dist/collapsible.js +37 -0
- package/dist/collapsible.js.map +1 -0
- package/dist/component-definitions.d.ts +16 -0
- package/dist/component-definitions.d.ts.map +1 -1
- package/dist/component-definitions.js +16 -0
- package/dist/component-definitions.js.map +1 -1
- package/dist/component-recipes.d.ts +20 -3
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +24 -1
- package/dist/component-recipes.js.map +1 -1
- package/dist/context-menu.d.ts +53 -0
- package/dist/context-menu.d.ts.map +1 -0
- package/dist/context-menu.js +44 -0
- package/dist/context-menu.js.map +1 -0
- package/dist/counter-badge-recipe.d.ts +5 -0
- package/dist/counter-badge-recipe.d.ts.map +1 -1
- package/dist/counter-badge-recipe.js +5 -0
- package/dist/counter-badge-recipe.js.map +1 -1
- package/dist/dataviz.d.ts +76 -0
- package/dist/dataviz.d.ts.map +1 -0
- package/dist/dataviz.js +58 -0
- package/dist/dataviz.js.map +1 -0
- package/dist/date-range.d.ts +56 -0
- package/dist/date-range.d.ts.map +1 -0
- package/dist/date-range.js +79 -0
- package/dist/date-range.js.map +1 -0
- package/dist/design-system-provider.d.ts +24 -0
- package/dist/design-system-provider.d.ts.map +1 -1
- package/dist/design-system-provider.js +21 -0
- package/dist/design-system-provider.js.map +1 -1
- package/dist/floating-action-button.d.ts +1 -1
- package/dist/floating-action-button.d.ts.map +1 -1
- package/dist/floating-action-button.js +3 -1
- package/dist/floating-action-button.js.map +1 -1
- package/dist/formatters.d.ts +37 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +69 -0
- package/dist/formatters.js.map +1 -0
- package/dist/heading.d.ts +82 -0
- package/dist/heading.d.ts.map +1 -0
- package/dist/heading.js +48 -0
- package/dist/heading.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/menubar.d.ts +133 -0
- package/dist/menubar.d.ts.map +1 -0
- package/dist/menubar.js +80 -0
- package/dist/menubar.js.map +1 -0
- package/dist/native-platform.d.ts +88 -0
- package/dist/native-platform.d.ts.map +1 -0
- package/dist/native-platform.js +70 -0
- package/dist/native-platform.js.map +1 -0
- package/dist/popover.d.ts +19 -4
- package/dist/popover.d.ts.map +1 -1
- package/dist/popover.js +3 -0
- package/dist/popover.js.map +1 -1
- package/dist/progress-recipe.d.ts +14 -0
- package/dist/progress-recipe.d.ts.map +1 -1
- package/dist/progress-recipe.js +14 -1
- package/dist/progress-recipe.js.map +1 -1
- package/dist/provider-button.d.ts +140 -0
- package/dist/provider-button.d.ts.map +1 -0
- package/dist/provider-button.js +83 -0
- package/dist/provider-button.js.map +1 -0
- package/dist/recipes.d.ts +15 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +14 -0
- package/dist/recipes.js.map +1 -1
- package/dist/sheet.d.ts +16 -0
- package/dist/sheet.d.ts.map +1 -1
- package/dist/sheet.js +33 -0
- package/dist/sheet.js.map +1 -1
- package/dist/sidebar.d.ts +168 -0
- package/dist/sidebar.d.ts.map +1 -0
- package/dist/sidebar.js +98 -0
- package/dist/sidebar.js.map +1 -0
- package/dist/skip-nav.d.ts +67 -0
- package/dist/skip-nav.d.ts.map +1 -0
- package/dist/skip-nav.js +47 -0
- package/dist/skip-nav.js.map +1 -0
- package/dist/tags-input.d.ts +157 -0
- package/dist/tags-input.d.ts.map +1 -0
- package/dist/tags-input.js +108 -0
- package/dist/tags-input.js.map +1 -0
- package/dist/text-formats.d.ts +85 -0
- package/dist/text-formats.d.ts.map +1 -0
- package/dist/text-formats.js +43 -0
- package/dist/text-formats.js.map +1 -0
- package/dist/toggle-group.d.ts +111 -0
- package/dist/toggle-group.d.ts.map +1 -0
- package/dist/toggle-group.js +78 -0
- package/dist/toggle-group.js.map +1 -0
- package/dist/top.d.ts +113 -0
- package/dist/top.d.ts.map +1 -0
- package/dist/top.js +72 -0
- package/dist/top.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/agreement.md +33 -0
- package/docs/anchor.md +59 -59
- package/docs/ant-design-coverage.md +5 -3
- package/docs/asset.md +26 -0
- package/docs/bottom-info.md +15 -0
- package/docs/breadcrumb.md +15 -15
- package/docs/button-label.md +20 -0
- package/docs/calendar.md +82 -154
- package/docs/carousel.md +27 -2
- package/docs/cascader.md +17 -0
- package/docs/chart.md +33 -0
- package/docs/clipboard.md +13 -0
- package/docs/collapsible.md +19 -0
- package/docs/command-palette.md +19 -0
- package/docs/confirm-popover.md +11 -1
- package/docs/context-menu.md +23 -0
- package/docs/data-table.md +22 -0
- package/docs/date-range.md +31 -0
- package/docs/density.md +23 -0
- package/docs/expansion-roadmap.md +16 -3
- package/docs/floating-action-button.md +35 -3
- package/docs/formatters.md +18 -0
- package/docs/generated/component-maturity.md +39 -23
- package/docs/generated/renderer-evidence.json +2921 -642
- package/docs/generated/renderer-evidence.md +53 -6
- package/docs/generated/showcase-manifest.json +1087 -64
- package/docs/heading.md +25 -0
- package/docs/identity.md +5 -0
- package/docs/layout.md +1 -1
- package/docs/library-gap-analysis.md +29 -9
- package/docs/list-row.md +14 -0
- package/docs/mentions.md +18 -0
- package/docs/menubar.md +22 -0
- package/docs/native-platform.md +36 -0
- package/docs/overlay-stack.md +24 -0
- package/docs/pagination.md +16 -8
- package/docs/popover.md +42 -13
- package/docs/product-audit-2026-09-15.md +88 -0
- package/docs/progress.md +16 -0
- package/docs/provider-button.md +33 -0
- package/docs/rating.md +19 -0
- package/docs/react-native-completion.md +347 -0
- package/docs/screen-chrome.md +52 -0
- package/docs/side-panel.md +23 -2
- package/docs/sidebar.md +30 -0
- package/docs/skip-nav.md +20 -0
- package/docs/splitter.md +22 -2
- package/docs/stable-promotion.md +51 -0
- package/docs/tags-input.md +25 -0
- package/docs/text-formats.md +15 -0
- package/docs/theming.md +84 -0
- package/docs/time-picker.md +22 -1
- package/docs/toast.md +18 -0
- package/docs/toggle-group.md +21 -0
- package/docs/top.md +32 -0
- package/docs/tour.md +23 -2
- package/docs/transfer-list.md +20 -0
- package/docs/tree-select.md +18 -0
- package/docs/tree.md +21 -0
- package/package.json +120 -6
package/dist/top.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { heading, spacing, typography } from "./foundations.js";
|
|
2
|
+
import { semanticColors } from "./semantic-colors.js";
|
|
3
|
+
export const topDefaults = {
|
|
4
|
+
size: "large",
|
|
5
|
+
headingLevel: 1,
|
|
6
|
+
};
|
|
7
|
+
function assertNonEmpty(value, field) {
|
|
8
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
9
|
+
throw new TypeError(`Top ${field} must not be empty`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function validateTopDescriptor(descriptor) {
|
|
13
|
+
if (descriptor === null || typeof descriptor !== "object") {
|
|
14
|
+
throw new TypeError("Top descriptor must be an object");
|
|
15
|
+
}
|
|
16
|
+
assertNonEmpty(descriptor.title, "title");
|
|
17
|
+
if (descriptor.eyebrow !== undefined)
|
|
18
|
+
assertNonEmpty(descriptor.eyebrow, "eyebrow");
|
|
19
|
+
if (descriptor.description !== undefined) {
|
|
20
|
+
assertNonEmpty(descriptor.description, "description");
|
|
21
|
+
}
|
|
22
|
+
if (descriptor.size !== undefined && descriptor.size !== "medium" && descriptor.size !== "large") {
|
|
23
|
+
throw new TypeError(`Unsupported Top size: ${String(descriptor.size)}`);
|
|
24
|
+
}
|
|
25
|
+
if (descriptor.headingLevel !== undefined &&
|
|
26
|
+
![1, 2, 3].includes(descriptor.headingLevel)) {
|
|
27
|
+
throw new TypeError(`Unsupported Top headingLevel: ${String(descriptor.headingLevel)}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `large`는 화면의 첫 제목, `medium`은 시트·모달 안의 첫 제목이다. 두 단계뿐인 이유는
|
|
32
|
+
* 세 번째가 필요해지는 자리가 곧 `Section`이기 때문이다 — 크기를 더 늘리는 대신 다른
|
|
33
|
+
* 컴포넌트를 쓰라는 신호로 둔다.
|
|
34
|
+
*/
|
|
35
|
+
export const topRecipe = {
|
|
36
|
+
slots: ["root", "eyebrow", "title", "description", "trailing"],
|
|
37
|
+
defaults: { size: topDefaults.size },
|
|
38
|
+
sizes: {
|
|
39
|
+
medium: { title: typography.titleLarge, paddingTop: spacing.md, paddingBottom: spacing.sm },
|
|
40
|
+
large: { title: heading.level2, paddingTop: spacing.xl, paddingBottom: spacing.md },
|
|
41
|
+
},
|
|
42
|
+
eyebrow: {
|
|
43
|
+
color: semanticColors.content.brand,
|
|
44
|
+
textVariant: "label",
|
|
45
|
+
marginBottom: spacing.xxs,
|
|
46
|
+
},
|
|
47
|
+
title: { color: semanticColors.content.primary },
|
|
48
|
+
description: {
|
|
49
|
+
color: semanticColors.content.body,
|
|
50
|
+
textVariant: "body",
|
|
51
|
+
marginTop: spacing.xs,
|
|
52
|
+
},
|
|
53
|
+
/** 제목 줄 오른쪽 보조 행동. 본문이므로 TopBar처럼 아이콘 전용을 강제하지 않는다. */
|
|
54
|
+
trailing: { gap: spacing.xs },
|
|
55
|
+
gap: spacing.xs,
|
|
56
|
+
};
|
|
57
|
+
export const topBehavior = {
|
|
58
|
+
/** 상태 축이 없다 — 화면이 제목을 바꾸면 그냥 다른 문자열을 넘긴다. */
|
|
59
|
+
controlled: [],
|
|
60
|
+
inputs: ["title", "eyebrow", "description", "size", "headingLevel"],
|
|
61
|
+
stateAxes: {},
|
|
62
|
+
web: { roles: ["heading"], keyboard: [], focus: "none" },
|
|
63
|
+
native: { roles: ["header"], states: [], actions: [] },
|
|
64
|
+
scenarios: [
|
|
65
|
+
"the-title-is-a-real-heading-element-at-the-declared-level-not-styled-text",
|
|
66
|
+
"top-is-body-content-that-scrolls-away-while-topbar-is-fixed-chrome",
|
|
67
|
+
"description-wraps-instead-of-truncating-so-large-text-never-hides-the-question",
|
|
68
|
+
"a-trailing-action-shares-the-title-row-and-drops-below-it-when-space-runs-out",
|
|
69
|
+
"eyebrow-never-carries-information-the-title-does-not-repeat-in-some-form",
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=top.js.map
|
package/dist/top.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"top.js","sourceRoot":"","sources":["../src/top.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { heading, spacing, typography } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * 화면 본문의 **첫 블록**이다. `TopBar`(상단 내비게이션 바)와 다른 자리이고, 그 차이가\n * 이 계약의 존재 이유다.\n *\n * - `TopBar`는 화면에 고정된 크롬이다 — 뒤로가기, 화면 이름, 액션. 스크롤과 무관하게\n * 붙어 있고 safe area를 다룬다.\n * - `Top`은 본문이다 — 스크롤과 함께 올라가고, 사용자가 \"이 화면이 무엇을 묻는지\"\n * 읽는 문장이 여기 있다. 토스 TDS가 \"Top과 ListRow로 화면 대부분을 만든다\"고\n * 말하는 그 Top이고, 지금까지 BurnTok `AppScreenHeader`·Taground `screen-shell`·\n * Diairy `DetailPanel`이 각자 다시 만들던 블록이다.\n *\n * `Section`과도 다르다. Section은 본문 **중간**의 묶음 제목이라 heading level이 화면\n * 구조에 종속되지만, Top은 화면당 하나뿐인 첫 제목이라 기본이 `h1`이다. 그래서 두\n * 계약을 합치지 않았다 — 합치면 \"이 Section이 화면의 h1인가\"를 매번 물어야 한다.\n */\nexport type TopSize = \"medium\" | \"large\";\n\nexport type TopDescriptor = Readonly<{\n title: string;\n /** 제목 위 한 줄. 카테고리·단계처럼 제목을 한정하는 짧은 말. */\n eyebrow?: string;\n /** 제목 아래 보조 문장. 길면 줄바꿈되며 잘리지 않는다. */\n description?: string;\n size?: TopSize;\n /**\n * 문서 구조상의 제목 단계. 화면당 하나인 Top은 `1`이 기본이지만, 한 라우트가\n * 여러 화면을 담는 경우(시트 안의 화면 등) 제품이 낮출 수 있다.\n */\n headingLevel?: 1 | 2 | 3;\n}>;\n\nexport const topDefaults = {\n size: \"large\",\n headingLevel: 1,\n} as const satisfies Readonly<{ size: TopSize; headingLevel: 1 | 2 | 3 }>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Top ${field} must not be empty`);\n }\n}\n\nexport function validateTopDescriptor(descriptor: TopDescriptor): void {\n if (descriptor === null || typeof descriptor !== \"object\") {\n throw new TypeError(\"Top descriptor must be an object\");\n }\n assertNonEmpty(descriptor.title, \"title\");\n if (descriptor.eyebrow !== undefined) assertNonEmpty(descriptor.eyebrow, \"eyebrow\");\n if (descriptor.description !== undefined) {\n assertNonEmpty(descriptor.description, \"description\");\n }\n if (descriptor.size !== undefined && descriptor.size !== \"medium\" && descriptor.size !== \"large\") {\n throw new TypeError(`Unsupported Top size: ${String(descriptor.size)}`);\n }\n if (\n descriptor.headingLevel !== undefined &&\n ![1, 2, 3].includes(descriptor.headingLevel)\n ) {\n throw new TypeError(`Unsupported Top headingLevel: ${String(descriptor.headingLevel)}`);\n }\n}\n\n/**\n * `large`는 화면의 첫 제목, `medium`은 시트·모달 안의 첫 제목이다. 두 단계뿐인 이유는\n * 세 번째가 필요해지는 자리가 곧 `Section`이기 때문이다 — 크기를 더 늘리는 대신 다른\n * 컴포넌트를 쓰라는 신호로 둔다.\n */\nexport const topRecipe = {\n slots: [\"root\", \"eyebrow\", \"title\", \"description\", \"trailing\"] as const,\n defaults: { size: topDefaults.size },\n sizes: {\n medium: { title: typography.titleLarge, paddingTop: spacing.md, paddingBottom: spacing.sm },\n large: { title: heading.level2, paddingTop: spacing.xl, paddingBottom: spacing.md },\n },\n eyebrow: {\n color: semanticColors.content.brand,\n textVariant: \"label\" as const,\n marginBottom: spacing.xxs,\n },\n title: { color: semanticColors.content.primary },\n description: {\n color: semanticColors.content.body,\n textVariant: \"body\" as const,\n marginTop: spacing.xs,\n },\n /** 제목 줄 오른쪽 보조 행동. 본문이므로 TopBar처럼 아이콘 전용을 강제하지 않는다. */\n trailing: { gap: spacing.xs },\n gap: spacing.xs,\n} as const satisfies {\n slots: readonly [\"root\", \"eyebrow\", \"title\", \"description\", \"trailing\"];\n defaults: { size: TopSize };\n sizes: Readonly<{\n medium: { title: typeof typography.titleLarge; paddingTop: number; paddingBottom: number };\n large: { title: typeof heading.level2; paddingTop: number; paddingBottom: number };\n }>;\n eyebrow: { color: ColorReference; textVariant: \"label\"; marginBottom: number };\n title: { color: ColorReference };\n description: { color: ColorReference; textVariant: \"body\"; marginTop: number };\n trailing: { gap: number };\n gap: number;\n};\n\nexport const topBehavior = {\n /** 상태 축이 없다 — 화면이 제목을 바꾸면 그냥 다른 문자열을 넘긴다. */\n controlled: [],\n inputs: [\"title\", \"eyebrow\", \"description\", \"size\", \"headingLevel\"],\n stateAxes: {},\n web: { roles: [\"heading\"], keyboard: [], focus: \"none\" },\n native: { roles: [\"header\"], states: [], actions: [] },\n scenarios: [\n \"the-title-is-a-real-heading-element-at-the-declared-level-not-styled-text\",\n \"top-is-body-content-that-scrolls-away-while-topbar-is-fixed-chrome\",\n \"description-wraps-instead-of-truncating-so-large-text-never-hides-the-question\",\n \"a-trailing-action-shares-the-title-row-and-drops-below-it-when-space-runs-out\",\n \"eyebrow-never-carries-information-the-title-does-not-repeat-in-some-form\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAgB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAgB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.2.0\" as const;\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Agreement contract
|
|
2
|
+
|
|
3
|
+
**문제.** 가입·결제 앞에 서는 약관 동의 묶음이다. 전체 동의 한 줄과 개별 항목들,
|
|
4
|
+
필수/선택 구분, 각 항목의 전문 보기가 한 덩어리로 움직인다.
|
|
5
|
+
|
|
6
|
+
**왜 CheckboxGroup으로 충분하지 않은가.** 세 가지가 체크박스 목록이 아니라 절차이기
|
|
7
|
+
때문이다.
|
|
8
|
+
|
|
9
|
+
1. **필수/선택이 제출 가능 여부를 정한다.** CheckboxGroup은 "몇 개 골랐는가"만 알고
|
|
10
|
+
"이 화면이 진행 가능한가"는 모른다. `resolveAgreementState`의 `satisfied`와
|
|
11
|
+
`missingRequiredIds`가 그 판정이고, 제품의 제출 버튼이 그것만 읽는다.
|
|
12
|
+
2. **전체 동의는 파생이다.** 저장되는 값은 개별 항목뿐이고 전체 행은 tri-state로
|
|
13
|
+
그려진다 — DataTable 머리글(`resolveDataTableSelectAllState`)·TreeSelect 부모
|
|
14
|
+
(`resolveTreeCheckedStates`)와 같은 관계다. 전체 동의를 별도 값으로 저장하면
|
|
15
|
+
"전체 동의했지만 개별은 두 개만 체크됨" 같은 모순이 표현 가능해진다.
|
|
16
|
+
3. **읽을 수 있어야 동의다.** 전문 경로가 없는 동의 항목은 만들 수 없게 두지는 않지만,
|
|
17
|
+
`detail`을 계약된 행동으로 둬서 renderer가 장식 화살표로 처리하지 못하게 한다.
|
|
18
|
+
그 컨트롤은 체크박스와 **다른 tab stop**이고 누르면 절대 체크되지 않는다.
|
|
19
|
+
|
|
20
|
+
**막아 둔 조합.** 필수이면서 비활성인 항목은 descriptor 단계에서 거절한다. 허용하면
|
|
21
|
+
사용자가 영원히 진행할 수 없는 화면이 조용히 만들어지고, "버튼이 왜 안 눌리는가"를
|
|
22
|
+
아무도 설명할 수 없다.
|
|
23
|
+
|
|
24
|
+
**비활성 항목의 회계.** 전체 동의의 분모에서 뺀다 — 사용자가 바꿀 수 없는 것을 "덜
|
|
25
|
+
동의했다"고 셀 수 없다. 필수는 비활성일 수 없으므로 `satisfied` 판정에는 이 예외가 없다.
|
|
26
|
+
|
|
27
|
+
**제품이 소유하는 것.** 문구, 링크 주소, 법적 유효성, 동의 기록의 저장·증빙. HJM은
|
|
28
|
+
판정과 배치만 갖는다. 약관 내용을 어디에 보여줄지(링크·시트·새 화면)도 제품 결정이라
|
|
29
|
+
`detail.href`는 선택이고, 없으면 renderer가 버튼으로 낸다.
|
|
30
|
+
|
|
31
|
+
**플랫폼 번역.** Web은 `group` + `checkbox` + 별도 링크/버튼, Native는 checkbox
|
|
32
|
+
accessibility state와 `openDetail` action이다. 두 표면 모두 전체 동의 행이 목록의 제목
|
|
33
|
+
역할을 하도록 `surface.sunken` 위에 놓는다.
|
package/docs/anchor.md
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
# Anchor —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- `
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
1
|
+
# Anchor — 같은 문서 안의 목차
|
|
2
|
+
|
|
3
|
+
2026-09-16 React/RN 확장 요청에 따라 Web 라이브러리 beta를 제공합니다. 이전에는 실제 제품의
|
|
4
|
+
긴 문서 수요가 없어 planned였습니다. 이번에는 작동하는 문서 예제와 브라우저 검증까지
|
|
5
|
+
제공하며 제품 채택·보조기기 검증·stable 승격은 별도로 남깁니다.
|
|
6
|
+
|
|
7
|
+
[Ant Design Anchor](https://ant.design/components/anchor/)의 문서 내 링크·현재 위치·스크롤
|
|
8
|
+
영역 구분을 비교했습니다. HJM은 content-brand 글자와 얇은 논리 방향 표시선으로 위치를
|
|
9
|
+
보여주며 별도 카드나 주요 행동 버튼으로 강조하지 않습니다. API 호환을 약속하지 않습니다.
|
|
10
|
+
|
|
11
|
+
## 사용법
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Anchor } from "@hjmds/react/anchor";
|
|
15
|
+
|
|
16
|
+
<Anchor label="가이드 목차" items={[
|
|
17
|
+
{ id: "start", label: "시작하기" },
|
|
18
|
+
{ id: "next", label: "다음 단계" },
|
|
19
|
+
]} offset={64} />
|
|
20
|
+
// 같은 문서에 고유 ID를 가진 <section id="start">, <section id="next">를 둡니다.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `items`는 비어 있지 않은 `{ id, label }` 배열입니다. ID는 공백 없이 문서 안에서 고유해야
|
|
24
|
+
합니다. 중복 ID·빈 label을 거부합니다. fragment는 ID를 URL encode해 생성합니다.
|
|
25
|
+
- `container` 생략 시 문서 스크롤을 관찰합니다. `HTMLElement`를 넘기면 해당 영역 내부만
|
|
26
|
+
관찰합니다. ref를 기다리는 `null`에서는 관찰하지 않습니다.
|
|
27
|
+
- `offset`은 상단 고정 헤더 아래에 남길 CSS px입니다. 기본 0이고 유한한 음이 아닌 수만
|
|
28
|
+
허용합니다. 별도 스크롤 영역의 border와 scrollTop을 포함해 계산합니다.
|
|
29
|
+
- `orientation`은 `vertical` 기본값 또는 `horizontal`입니다. 좁은 폭에서 링크가 줄바꿈하며
|
|
30
|
+
긴 label을 자르지 않습니다. 목차의 sticky 배치와 문서 레이아웃은 호스트가 소유합니다.
|
|
31
|
+
- `historyMode`는 `push` 기본값, `replace`, `none`입니다. 앞의 둘은 기존 history.state를
|
|
32
|
+
보존하며 fragment만 갱신합니다. `none`은 URL을 쓰지 않는 내장 미리보기에 적합합니다.
|
|
33
|
+
- `onNavigate(id, event)`에서 `preventDefault()`로 앱 라우터 등에 동작을 위임할 수 있습니다.
|
|
34
|
+
새 탭을 여는 modifier 클릭은 브라우저에 맡깁니다. `ref`는 nav 요소를 가리킵니다.
|
|
35
|
+
|
|
36
|
+
## 스크롤·초점 계약
|
|
37
|
+
|
|
38
|
+
`nav > ul > li > a`를 사용하고 현재 링크 하나에 `aria-current="location"`을 둡니다.
|
|
39
|
+
대상이 DOM에 없는 링크는 현재 위치 후보에서 제외하며 기본 href 동작을 유지합니다.
|
|
40
|
+
사용자가 링크를 누르면 해당 section/제목에 초점을 옮깁니다. 일반 section은 임시 `tabindex=-1`을
|
|
41
|
+
받고 blur/unmount 때 복원합니다. Tab/Enter는 브라우저 링크 동작입니다.
|
|
42
|
+
|
|
43
|
+
현재 위치는 DOM 순서의 가정 대신 실제 위치로 계산합니다. 스크롤 끝에서는 짧은 마지막
|
|
44
|
+
섹션도 선택합니다. 스크롤·창 크기·대상/부모 크기·본문 삽입/삭제를 관찰하고 unmount 때
|
|
45
|
+
listener·observer·예약 frame을 해제합니다. CSS transform만으로 위치를 움직이는 별도 애니메이션은
|
|
46
|
+
계약에 포함하지 않습니다. window의 hashchange/popstate와 초기 fragment는 등록된 대상으로
|
|
47
|
+
즉시 이동하여 별도 스크롤 영역도 복구합니다.
|
|
48
|
+
|
|
49
|
+
일반 클릭은 smooth scroll입니다. HJM 환경 또는 OS가 reduced motion이면 `instant`로 이동합니다.
|
|
50
|
+
`auto`는 호스트의 scroll-behavior에 따라 다시 애니메이션될 수 있어 사용하지 않습니다.
|
|
51
|
+
|
|
52
|
+
## 범위와 검증
|
|
53
|
+
|
|
54
|
+
Web 전용입니다. Native 라우팅·목록 위치 이동은 해당 플랫폼과 제품이 소유하며 빈 RN wrapper를
|
|
55
|
+
추가하지 않습니다. 중첩 트리 목차·자동 제목 수집·오버레이 anchor positioning은 포함하지 않습니다.
|
|
56
|
+
|
|
57
|
+
`Patterns/Anchor`는 세 부분으로 나눈 읽기 가이드와 별도 스크롤 영역을 제공합니다.
|
|
58
|
+
브라우저 테스트는 위치 동기화·짧은 마지막 부분·offset·reduced motion·임시 focus 복원·
|
|
59
|
+
fragment 복구·나중에 삽입된 대상을 검사합니다. 실제 제품 문서 채택·보조기기 검증은 남아 있습니다.
|
|
@@ -53,7 +53,7 @@ message와 notification을 가르는 축(정보 층, 지속 시간, 동시 개
|
|
|
53
53
|
| 종류 | 뜻 | 예 | 재검토 신호 |
|
|
54
54
|
|---|---|---|---|
|
|
55
55
|
| **흡수됨** | 그 문제를 이미 다른 컴포넌트가 완결한다 | `Notification`→Toast, `Dropdown`→Menu, `ContextPanel`→SidePanel/Sheet, `Flex`→Stack, `TimePicker`→Select 조합, `Rating`→Slider/Statistic | 흡수한 쪽이 못 푸는 요구가 나올 때 |
|
|
56
|
-
| **검증할 화면이 없음** | 계약 자체는 유효하나 이를 확인할 제품 화면이 없다 | `Anchor
|
|
56
|
+
| **검증할 화면이 없음** | 계약 자체는 유효하나 이를 확인할 제품 화면이 없다 | `Anchor` | 그 화면이 실제로 생길 때 |
|
|
57
57
|
| **거절됨** | 계약도 유효하고 화면이 생겨도 만들지 않는다 | `BorderBeam`(장식), `AppProvider`(런타임뿐), `Utility`(실체 없음) | 정체성이나 아키텍처 경계가 바뀔 때만 |
|
|
58
58
|
| **흡수 대기** | 흡수 판정은 끝났으나 **선결 축이 아직 없다** | `Cascader`(TreeSelect에 `valueMode`/`commitAt`가 추가돼야 성립) | 그 축이 실제로 추가될 때 |
|
|
59
59
|
|
|
@@ -88,8 +88,10 @@ evidence registry로 판단합니다.
|
|
|
88
88
|
- partial maturity: decomposed target 중 일부만 stable 또는 beta
|
|
89
89
|
- planned only: 모든 target이 planned
|
|
90
90
|
|
|
91
|
-
2026-
|
|
92
|
-
|
|
91
|
+
2026-09-18 SidePanel·Splitter·Tour·Tree·TransferList·Mentions·CommandPalette·DataTable renderer까지 추가한 뒤 status 기반 분포는
|
|
92
|
+
**fully mature 59 / partial maturity 0 /
|
|
93
|
+
planned only 14**입니다. decomposed Drawer의 두 갈래(Sheet·SidePanel)가 모두 구현되면서
|
|
94
|
+
partial maturity가 비었습니다 — 0은 "부분 구현이 없다"는 뜻이고 planned only 20은 그대로입니다. 따라서 73/73 tracking은 73개 구현 완료를 의미하지 않습니다.
|
|
93
95
|
홈과 Component Explorer는 이 수치를 분리해 표시합니다. 이 숫자는 source inventory 수가
|
|
94
96
|
아니라 HJM target의 maturity에서 계산하므로 catalog status가 바뀌면 함께 갱신합니다.
|
|
95
97
|
|
package/docs/asset.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Asset contract
|
|
2
|
+
|
|
3
|
+
**문제.** 아이콘·이미지·Lottie·비디오를 **같은 액자**에 넣는 자리.
|
|
4
|
+
|
|
5
|
+
**Icon·Image·Avatar가 있는데 왜 또 두는가.** 셋은 각자 다른 모양 규칙을 갖는다 — 아이콘은
|
|
6
|
+
정사각, Avatar는 원, Image는 비율. 이들이 같은 줄에 섞여 나오면 크기와 모서리가 어긋난다.
|
|
7
|
+
에어리의 `LottieArt`·`AnimalArt`가 그 어긋남을 제품 안에서 다시 맞추고 있었다. Asset은
|
|
8
|
+
액자 규칙(크기 4단·모양 3종·겹침·보조 표식)만 갖는다.
|
|
9
|
+
|
|
10
|
+
**무엇을 그릴지는 슬롯으로 받는다.** Lottie 재생기도 비디오 재생기도 이 패키지가 의존하지
|
|
11
|
+
않는다. 그래서 이 계약에 재생 상태(play/pause/seek)가 없다 — 재생기는 제품 것이고, 여기서
|
|
12
|
+
정하는 것은 액자뿐이다. 이 경계를 흐리면 `@hjmds/react`가 애니메이션 런타임을 끌고 온다.
|
|
13
|
+
|
|
14
|
+
**뜻이 있는 자산에 이름이 없으면 거절한다.** 움직이는 그림일수록 "무엇을 뜻하는지"가
|
|
15
|
+
화면에 없으면 보조기기에서 사라진다. 반대로 `decorative`와 `accessibilityLabel`을 **함께**
|
|
16
|
+
주는 것도 거절한다 — 조용히 한쪽을 무시하면 이름을 지운 줄 모른다.
|
|
17
|
+
|
|
18
|
+
**`reducedMotion`에서는 숨기지 않고 정지한다.** 그 자리에 그림이 있다는 사실 자체가 화면의
|
|
19
|
+
뜻인 경우가 많다(에어리의 동물). 정지 화면을 제품이 주지 못하면 재생만 멈춘다.
|
|
20
|
+
`shouldAnimateAsset(kind, reducedMotion)`이 이 판단을 갖는다.
|
|
21
|
+
|
|
22
|
+
**보조 표식은 액자 바깥 모서리에 붙는다.** 재생 아이콘이 매체 위에 올라가면 가장 중요한
|
|
23
|
+
프레임 가운데를 가린다.
|
|
24
|
+
|
|
25
|
+
**겹침 비율은 Avatar와 같은 값이다.** 한 줄에 섞여 나오는 것이 이 컴포넌트가 생긴 이유라
|
|
26
|
+
두 값이 다르면 그 줄이 다시 어긋난다.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# BottomInfo contract
|
|
2
|
+
|
|
3
|
+
**문제.** 주 행동 **아래** 붙는 작은 안내 — "가입하면 약관에 동의하는 것으로 봅니다",
|
|
4
|
+
"수수료는 결제 시점에 확정됩니다". 토스 TDS의 BottomInfo와 같은 자리다.
|
|
5
|
+
|
|
6
|
+
**Notice와 다르다.** Notice는 **지금 생긴 상태**를 알린다(오류·성공·경고). BottomInfo는
|
|
7
|
+
화면에 늘 있는 **조건**이다. 그래서 tone에 danger/success가 없고, 아이콘이 없고,
|
|
8
|
+
`role="status"`도 없다 — 상태 변화로 발표하면 사용자가 매번 "무슨 문제가 생겼나" 하고
|
|
9
|
+
읽게 된다. 두 단계(`muted`/`emphasis`)만 두는 이유도 같다: 강조는 있어도 경보는 없다.
|
|
10
|
+
|
|
11
|
+
**한 줄과 여러 줄이 다르게 읽힌다.** 한 줄은 문장, 여러 줄은 목록이다. 한 줄에 점을
|
|
12
|
+
찍으면 오히려 시끄러워서 `listMarkerFrom`으로 경계를 계약에 적었다.
|
|
13
|
+
|
|
14
|
+
**문장 안의 링크는 제품 것이다.** 약관 링크의 주소·라우팅은 제품이 소유하므로
|
|
15
|
+
`renderItem`으로 그 줄만 대체한다.
|
package/docs/breadcrumb.md
CHANGED
|
@@ -28,8 +28,7 @@ Breadcrumb는 새 href 개념을 만들지 않습니다. 조상 항목의 `desti
|
|
|
28
28
|
`LinkDestination`(`internal | external`) 타입 그대로이고, `validateBreadcrumbDescriptor`는
|
|
29
29
|
각 조상 항목마다 `validateLinkDestination`을 그대로 호출합니다. 그래서 internal href가
|
|
30
30
|
`/`, `?`, `#`로 시작해야 한다거나 external href가 허용된 protocol만 써야 한다는 규칙은
|
|
31
|
-
Link 문서(`docs/link.md`)가 유일한 출처입니다. Breadcrumb 조상 항목은 Web
|
|
32
|
-
anchor, Native가 이 컴포넌트를 쓴다면 Expo Router Link로 렌더링될 항목이라는 뜻이며,
|
|
31
|
+
Link 문서(`docs/link.md`)가 유일한 출처입니다. Breadcrumb 조상 항목은 Web의 실제 anchor로 렌더링하며,
|
|
33
32
|
`Link`의 `disabled`/`onClick`/`onPress` 금지 규칙도 그대로 상속합니다.
|
|
34
33
|
|
|
35
34
|
## HJM 기본값
|
|
@@ -38,14 +37,13 @@ anchor, Native가 이 컴포넌트를 쓴다면 Expo Router Link로 렌더링될
|
|
|
38
37
|
- 구분자(`/`, `›`)는 정보가 아니라 장식입니다. `breadcrumbRecipe.separator.decorative`는
|
|
39
38
|
항상 `true`이고 renderer는 이를 접근성 트리에서 숨깁니다(Web `aria-hidden`, 스크린
|
|
40
39
|
리더는 순서만 듣습니다).
|
|
41
|
-
- 구분자
|
|
42
|
-
|
|
40
|
+
- 기본 구분자 `›`만 RTL에서 미러링합니다. `separator`로 전달한 Icon이나 문자는 소비자가
|
|
41
|
+
방향을 소유하므로 다시 뒤집지 않습니다. `separator={null}`은 구분자를 숨깁니다.
|
|
43
42
|
- **축약(`...`)을 넣지 않습니다.** 항목이 많을 때 가운데를 접는 것은 실제 화면에서
|
|
44
43
|
측정된 수요가 아직 없습니다. 필요해지면 별도 `collapsed` 축으로 명시적으로 추가하고,
|
|
45
44
|
지금은 renderer가 전체 trail을 그대로 그립니다.
|
|
46
45
|
- 크기는 Link의 inline 취급을 따릅니다 — 44-unit 최소 target을 강제하지 않고 밑줄과
|
|
47
|
-
focus indicator만 유지합니다.
|
|
48
|
-
standalone Link처럼 하나씩 별도 target으로 쓰기보다, 촘촘한 한 줄 trail로 배치됩니다.
|
|
46
|
+
focus indicator만 유지합니다. 긴 경로는 줄바꿈하고 전체 항목을 유지합니다. 링크의 밑줄과 focus indicator는 유지합니다.
|
|
49
47
|
|
|
50
48
|
## 플랫폼 번역 — 왜 Web 전용인가
|
|
51
49
|
|
|
@@ -70,13 +68,15 @@ Web에서는:
|
|
|
70
68
|
가져오므로 Breadcrumb 자체가 새 키보드 상호작용을 정의하지 않습니다.
|
|
71
69
|
- 현재 항목은 tab stop이 아니고 `aria-current="page"`만 갖습니다.
|
|
72
70
|
|
|
73
|
-
## 검증
|
|
71
|
+
## 공개 경로와 검증
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
`import { Breadcrumb } from "@hjmds/react/breadcrumb"`로 가져옵니다. root와 기존 navigation
|
|
74
|
+
경로도 유지합니다. 필수 props는 `label`, `items`이고 `ref`는 nav 요소를 가리킵니다.
|
|
75
|
+
|
|
76
|
+
2026-09-16 사용자의 React/RN 라이브러리 확장 요청으로, 제품 채택 대기와 라이브러리 beta
|
|
77
|
+
제공을 분리했습니다. `Patterns/WebNavigation`은 조상 링크로 보관함을 열고 다시 기록 목록으로
|
|
78
|
+
돌아오는 작동 예제입니다. 제품 채택이나 stable 증거로 계산하지 않습니다.
|
|
79
|
+
|
|
80
|
+
[WAI Breadcrumb](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/)의 landmark·조상 링크·
|
|
81
|
+
현재 위치 의미를 확인했습니다. 브라우저 테스트는 실제 anchor, 현재 plain text, 장식 구분자,
|
|
82
|
+
320px/2배 글자·RTL 줄바꿈을 다룹니다. 실제 제품 라우팅·보조기기 검증은 남아 있습니다.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 버튼 라벨 줄바꿈 정책
|
|
2
|
+
|
|
3
|
+
**문제.** 긴 라벨의 처리 규칙이 recipe에 없어서 Web은 무한히 줄바꿈하고 Native는 한 줄로
|
|
4
|
+
잘렸다. **같은 번역문이 두 표면에서 다르게 보였다.**
|
|
5
|
+
|
|
6
|
+
**규칙.** 자르지 않고 **두 줄까지 접는다**(`buttonRecipe.label.maxLines = 2`).
|
|
7
|
+
|
|
8
|
+
- **자르지 않는 이유**: 잘린 행동 라벨("삭제하…")은 무엇을 하는 버튼인지 잃는다. 링크
|
|
9
|
+
텍스트나 본문과 달리 버튼 라벨은 그 자체가 행동의 정의다.
|
|
10
|
+
- **무한히 늘리지 않는 이유**: 하단 CTA 행처럼 버튼이 남의 레이아웃을 밀어내는 자리가
|
|
11
|
+
있다. 두 줄을 넘는 라벨은 레이아웃이 아니라 **카피의 문제**이고, 그 신호를 화면이
|
|
12
|
+
주는 편이 낫다.
|
|
13
|
+
|
|
14
|
+
**큰 글자 설정에서는 상한을 푼다**(`liftCapOnLargeText`). 사용자가 키운 글자에서 두 줄로
|
|
15
|
+
자르면 내용이 사라지고 그건 WCAG 1.4.4다. 제품 카피가 아니라 사용자 설정이 원인이므로
|
|
16
|
+
같은 규칙을 적용할 수 없다. `resolveButtonLabelLines(largeText)`가 이 판정을 갖고,
|
|
17
|
+
Web은 `-webkit-line-clamp`, Native는 `numberOfLines`로 같은 답을 쓴다.
|
|
18
|
+
|
|
19
|
+
**숫자는 recipe에만 있다.** 스타일시트는 `--hjm-button-label-lines`를 읽고 렌더러가 그
|
|
20
|
+
변수를 recipe에서 채운다 — 두 벌이 되면 언젠가 어긋난다.
|