@hjmds/design-contracts 0.8.2
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/README.md +208 -0
- package/dist/alert-dialog.d.ts +102 -0
- package/dist/alert-dialog.d.ts.map +1 -0
- package/dist/alert-dialog.js +136 -0
- package/dist/alert-dialog.js.map +1 -0
- package/dist/base-recipes.d.ts +184 -0
- package/dist/base-recipes.d.ts.map +1 -0
- package/dist/base-recipes.js +129 -0
- package/dist/base-recipes.js.map +1 -0
- package/dist/behaviors.d.ts +1254 -0
- package/dist/behaviors.d.ts.map +1 -0
- package/dist/behaviors.js +972 -0
- package/dist/behaviors.js.map +1 -0
- package/dist/bottom-navigation-defaults.d.ts +13 -0
- package/dist/bottom-navigation-defaults.d.ts.map +1 -0
- package/dist/bottom-navigation-defaults.js +10 -0
- package/dist/bottom-navigation-defaults.js.map +1 -0
- package/dist/bottom-navigation.d.ts +114 -0
- package/dist/bottom-navigation.d.ts.map +1 -0
- package/dist/bottom-navigation.js +223 -0
- package/dist/bottom-navigation.js.map +1 -0
- package/dist/breadcrumb.d.ts +97 -0
- package/dist/breadcrumb.d.ts.map +1 -0
- package/dist/breadcrumb.js +99 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/calendar.d.ts +285 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +297 -0
- package/dist/calendar.js.map +1 -0
- package/dist/card.d.ts +39 -0
- package/dist/card.d.ts.map +1 -0
- package/dist/card.js +50 -0
- package/dist/card.js.map +1 -0
- package/dist/carousel.d.ts +180 -0
- package/dist/carousel.d.ts.map +1 -0
- package/dist/carousel.js +172 -0
- package/dist/carousel.js.map +1 -0
- package/dist/catalog.d.ts +7158 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +220 -0
- package/dist/catalog.js.map +1 -0
- package/dist/collection.d.ts +123 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +211 -0
- package/dist/collection.js.map +1 -0
- package/dist/color-references.d.ts +46 -0
- package/dist/color-references.d.ts.map +1 -0
- package/dist/color-references.js +39 -0
- package/dist/color-references.js.map +1 -0
- package/dist/colors.d.ts +56 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +90 -0
- package/dist/colors.js.map +1 -0
- package/dist/command-palette.d.ts +240 -0
- package/dist/command-palette.d.ts.map +1 -0
- package/dist/command-palette.js +97 -0
- package/dist/command-palette.js.map +1 -0
- package/dist/component-contracts.d.ts +137 -0
- package/dist/component-contracts.d.ts.map +1 -0
- package/dist/component-contracts.js +53 -0
- package/dist/component-contracts.js.map +1 -0
- package/dist/component-definitions.d.ts +135 -0
- package/dist/component-definitions.d.ts.map +1 -0
- package/dist/component-definitions.js +142 -0
- package/dist/component-definitions.js.map +1 -0
- package/dist/component-recipes.d.ts +3583 -0
- package/dist/component-recipes.d.ts.map +1 -0
- package/dist/component-recipes.js +1503 -0
- package/dist/component-recipes.js.map +1 -0
- package/dist/component-references.d.ts +416 -0
- package/dist/component-references.d.ts.map +1 -0
- package/dist/component-references.js +140 -0
- package/dist/component-references.js.map +1 -0
- package/dist/content-state.d.ts +104 -0
- package/dist/content-state.d.ts.map +1 -0
- package/dist/content-state.js +116 -0
- package/dist/content-state.js.map +1 -0
- package/dist/counter-badge-recipe.d.ts +80 -0
- package/dist/counter-badge-recipe.d.ts.map +1 -0
- package/dist/counter-badge-recipe.js +44 -0
- package/dist/counter-badge-recipe.js.map +1 -0
- package/dist/counter-badge.d.ts +12 -0
- package/dist/counter-badge.d.ts.map +1 -0
- package/dist/counter-badge.js +21 -0
- package/dist/counter-badge.js.map +1 -0
- package/dist/data-table.d.ts +203 -0
- package/dist/data-table.d.ts.map +1 -0
- package/dist/data-table.js +182 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-picker.d.ts +268 -0
- package/dist/date-picker.d.ts.map +1 -0
- package/dist/date-picker.js +168 -0
- package/dist/date-picker.js.map +1 -0
- package/dist/description-list.d.ts +68 -0
- package/dist/description-list.d.ts.map +1 -0
- package/dist/description-list.js +85 -0
- package/dist/description-list.js.map +1 -0
- package/dist/design-system-provider.d.ts +80 -0
- package/dist/design-system-provider.d.ts.map +1 -0
- package/dist/design-system-provider.js +147 -0
- package/dist/design-system-provider.js.map +1 -0
- package/dist/evidence.d.ts +49 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +133 -0
- package/dist/evidence.js.map +1 -0
- package/dist/file-picker.d.ts +183 -0
- package/dist/file-picker.d.ts.map +1 -0
- package/dist/file-picker.js +224 -0
- package/dist/file-picker.js.map +1 -0
- package/dist/floating-action-button.d.ts +143 -0
- package/dist/floating-action-button.d.ts.map +1 -0
- package/dist/floating-action-button.js +149 -0
- package/dist/floating-action-button.js.map +1 -0
- package/dist/form.d.ts +143 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +206 -0
- package/dist/form.js.map +1 -0
- package/dist/foundations.d.ts +300 -0
- package/dist/foundations.d.ts.map +1 -0
- package/dist/foundations.js +238 -0
- package/dist/foundations.js.map +1 -0
- package/dist/grid.d.ts +75 -0
- package/dist/grid.d.ts.map +1 -0
- package/dist/grid.js +133 -0
- package/dist/grid.js.map +1 -0
- package/dist/icon-button-recipe.d.ts +99 -0
- package/dist/icon-button-recipe.d.ts.map +1 -0
- package/dist/icon-button-recipe.js +53 -0
- package/dist/icon-button-recipe.js.map +1 -0
- package/dist/icon.d.ts +41 -0
- package/dist/icon.d.ts.map +1 -0
- package/dist/icon.js +147 -0
- package/dist/icon.js.map +1 -0
- package/dist/image.d.ts +82 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +100 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +118 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +118 -0
- package/dist/layout.js.map +1 -0
- package/dist/link.d.ts +57 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +142 -0
- package/dist/link.js.map +1 -0
- package/dist/load-more.d.ts +64 -0
- package/dist/load-more.d.ts.map +1 -0
- package/dist/load-more.js +124 -0
- package/dist/load-more.js.map +1 -0
- package/dist/mentions.d.ts +67 -0
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +107 -0
- package/dist/mentions.js.map +1 -0
- package/dist/number-field.d.ts +208 -0
- package/dist/number-field.d.ts.map +1 -0
- package/dist/number-field.js +247 -0
- package/dist/number-field.js.map +1 -0
- package/dist/otp-field.d.ts +152 -0
- package/dist/otp-field.d.ts.map +1 -0
- package/dist/otp-field.js +117 -0
- package/dist/otp-field.js.map +1 -0
- package/dist/pagination.d.ts +181 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +226 -0
- package/dist/pagination.js.map +1 -0
- package/dist/password-field.d.ts +187 -0
- package/dist/password-field.d.ts.map +1 -0
- package/dist/password-field.js +120 -0
- package/dist/password-field.js.map +1 -0
- package/dist/popover.d.ts +152 -0
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.js +137 -0
- package/dist/popover.js.map +1 -0
- package/dist/progress-recipe.d.ts +43 -0
- package/dist/progress-recipe.d.ts.map +1 -0
- package/dist/progress-recipe.js +16 -0
- package/dist/progress-recipe.js.map +1 -0
- package/dist/recipes.d.ts +31 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/recipes.js +46 -0
- package/dist/recipes.js.map +1 -0
- package/dist/responsive.d.ts +27 -0
- package/dist/responsive.d.ts.map +1 -0
- package/dist/responsive.js +66 -0
- package/dist/responsive.js.map +1 -0
- package/dist/result.d.ts +111 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +97 -0
- package/dist/result.js.map +1 -0
- package/dist/selection-helpers.d.ts +16 -0
- package/dist/selection-helpers.d.ts.map +1 -0
- package/dist/selection-helpers.js +52 -0
- package/dist/selection-helpers.js.map +1 -0
- package/dist/semantic-colors.d.ts +275 -0
- package/dist/semantic-colors.d.ts.map +1 -0
- package/dist/semantic-colors.js +84 -0
- package/dist/semantic-colors.js.map +1 -0
- package/dist/sheet.d.ts +51 -0
- package/dist/sheet.d.ts.map +1 -0
- package/dist/sheet.js +69 -0
- package/dist/sheet.js.map +1 -0
- package/dist/showcase.d.ts +153 -0
- package/dist/showcase.d.ts.map +1 -0
- package/dist/showcase.js +210 -0
- package/dist/showcase.js.map +1 -0
- package/dist/side-panel.d.ts +199 -0
- package/dist/side-panel.d.ts.map +1 -0
- package/dist/side-panel.js +111 -0
- package/dist/side-panel.js.map +1 -0
- package/dist/slider.d.ts +138 -0
- package/dist/slider.d.ts.map +1 -0
- package/dist/slider.js +150 -0
- package/dist/slider.js.map +1 -0
- package/dist/splitter.d.ts +113 -0
- package/dist/splitter.d.ts.map +1 -0
- package/dist/splitter.js +99 -0
- package/dist/splitter.js.map +1 -0
- package/dist/statistic.d.ts +41 -0
- package/dist/statistic.d.ts.map +1 -0
- package/dist/statistic.js +76 -0
- package/dist/statistic.js.map +1 -0
- package/dist/steps.d.ts +196 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +160 -0
- package/dist/steps.js.map +1 -0
- package/dist/tag.d.ts +128 -0
- package/dist/tag.d.ts.map +1 -0
- package/dist/tag.js +93 -0
- package/dist/tag.js.map +1 -0
- package/dist/timeline.d.ts +147 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +127 -0
- package/dist/timeline.js.map +1 -0
- package/dist/toast.d.ts +164 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +529 -0
- package/dist/toast.js.map +1 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +9 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tooltip.d.ts +44 -0
- package/dist/tooltip.d.ts.map +1 -0
- package/dist/tooltip.js +88 -0
- package/dist/tooltip.js.map +1 -0
- package/dist/tour.d.ts +218 -0
- package/dist/tour.d.ts.map +1 -0
- package/dist/tour.js +211 -0
- package/dist/tour.js.map +1 -0
- package/dist/transfer-list.d.ts +207 -0
- package/dist/transfer-list.d.ts.map +1 -0
- package/dist/transfer-list.js +193 -0
- package/dist/transfer-list.js.map +1 -0
- package/dist/tree-select.d.ts +78 -0
- package/dist/tree-select.d.ts.map +1 -0
- package/dist/tree-select.js +132 -0
- package/dist/tree-select.js.map +1 -0
- package/dist/tree.d.ts +206 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +223 -0
- package/dist/tree.js.map +1 -0
- package/dist/upload-item.d.ts +173 -0
- package/dist/upload-item.d.ts.map +1 -0
- package/dist/upload-item.js +156 -0
- package/dist/upload-item.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/docs/affix.md +63 -0
- package/docs/anchor.md +59 -0
- package/docs/ant-design-coverage.md +119 -0
- package/docs/app-provider.md +50 -0
- package/docs/app-rn-adoption.md +227 -0
- package/docs/architecture.md +320 -0
- package/docs/authoring-brief.md +87 -0
- package/docs/border-beam.md +66 -0
- package/docs/bottom-navigation.md +127 -0
- package/docs/breadcrumb.md +82 -0
- package/docs/calendar.md +154 -0
- package/docs/carousel.md +130 -0
- package/docs/cascader.md +93 -0
- package/docs/catalog-decision-status.md +306 -0
- package/docs/color-picker.md +72 -0
- package/docs/command-palette.md +116 -0
- package/docs/confirm-popover.md +93 -0
- package/docs/consistency-audit.md +383 -0
- package/docs/consumer-release-gate.md +90 -0
- package/docs/content-state.md +156 -0
- package/docs/context-panel.md +88 -0
- package/docs/cross-platform-core-normalization.md +118 -0
- package/docs/data-table.md +79 -0
- package/docs/date-picker.md +84 -0
- package/docs/description-list.md +75 -0
- package/docs/design-system-provider.md +139 -0
- package/docs/dropdown.md +78 -0
- package/docs/expansion-roadmap.md +285 -0
- package/docs/file-picker.md +59 -0
- package/docs/floating-action-button.md +111 -0
- package/docs/form.md +134 -0
- package/docs/generated/component-maturity.md +103 -0
- package/docs/generated/renderer-evidence.json +5597 -0
- package/docs/generated/renderer-evidence.md +132 -0
- package/docs/generated/showcase-manifest.json +3732 -0
- package/docs/icon.md +22 -0
- package/docs/identity.md +121 -0
- package/docs/image.md +84 -0
- package/docs/implementation-0.5.md +85 -0
- package/docs/layout-primitives.md +107 -0
- package/docs/layout.md +83 -0
- package/docs/library-reference-decisions.md +123 -0
- package/docs/link.md +67 -0
- package/docs/load-more.md +31 -0
- package/docs/mentions.md +82 -0
- package/docs/migration-0.2.md +108 -0
- package/docs/migration-0.3.md +72 -0
- package/docs/migration-0.5.md +82 -0
- package/docs/migration-0.6.md +197 -0
- package/docs/notification.md +54 -0
- package/docs/number-field.md +82 -0
- package/docs/otp-field.md +103 -0
- package/docs/pagination.md +139 -0
- package/docs/password-field.md +98 -0
- package/docs/popover.md +124 -0
- package/docs/promotion-candidates.md +169 -0
- package/docs/qr-code.md +69 -0
- package/docs/rating.md +58 -0
- package/docs/responsive-grid.md +90 -0
- package/docs/result.md +96 -0
- package/docs/showcase.md +97 -0
- package/docs/side-panel.md +71 -0
- package/docs/slider.md +79 -0
- package/docs/splitter.md +55 -0
- package/docs/statistic.md +16 -0
- package/docs/steps.md +115 -0
- package/docs/tag.md +57 -0
- package/docs/time-picker.md +88 -0
- package/docs/timeline.md +146 -0
- package/docs/toast.md +127 -0
- package/docs/tooltip.md +58 -0
- package/docs/tour.md +136 -0
- package/docs/transfer-list.md +94 -0
- package/docs/tree-select.md +101 -0
- package/docs/tree.md +123 -0
- package/docs/upload-item.md +68 -0
- package/docs/utility.md +53 -0
- package/docs/virtual-list.md +70 -0
- package/docs/watermark.md +58 -0
- package/package.json +402 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
export type UploadItemStatus = "pending" | "uploading" | "success" | "error";
|
|
2
|
+
export type UploadItemPendingState = Readonly<{
|
|
3
|
+
status: "pending";
|
|
4
|
+
}>;
|
|
5
|
+
export type UploadItemUploadingState = Readonly<{
|
|
6
|
+
status: "uploading";
|
|
7
|
+
/** Product-measured transfer fraction. `null` means indeterminate — size unknown. */
|
|
8
|
+
progress: number | null;
|
|
9
|
+
/** Product-formatted announcement, e.g. "1.6MB / 3.2MB 업로드 중". */
|
|
10
|
+
progressLabel?: string;
|
|
11
|
+
}>;
|
|
12
|
+
export type UploadItemSuccessState = Readonly<{
|
|
13
|
+
status: "success";
|
|
14
|
+
}>;
|
|
15
|
+
export type UploadItemErrorState = Readonly<{
|
|
16
|
+
status: "error";
|
|
17
|
+
/** States the problem and the next action, e.g. "네트워크 오류입니다. 다시 시도해주세요." */
|
|
18
|
+
message: string;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Cancel and retry are never stored as separate booleans: deriving them from
|
|
22
|
+
* `status` (see `getUploadItemAvailableAction`) makes "cancel while error" or
|
|
23
|
+
* "retry while uploading" unrepresentable, the same way `SheetOpenState` and
|
|
24
|
+
* `LoadMoreState` close off invalid combinations by construction.
|
|
25
|
+
*/
|
|
26
|
+
export type UploadItemState = UploadItemPendingState | UploadItemUploadingState | UploadItemSuccessState | UploadItemErrorState;
|
|
27
|
+
export type UploadItemDescriptor = Readonly<{
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
/** Product-formatted size, e.g. "1.2 MB" — HJM does not format bytes. */
|
|
31
|
+
sizeLabel?: string;
|
|
32
|
+
state: UploadItemState;
|
|
33
|
+
}>;
|
|
34
|
+
export type UploadItemLabels = Readonly<{
|
|
35
|
+
pending: string;
|
|
36
|
+
/** Fallback when `progress` is `null` or `progressLabel` is absent. */
|
|
37
|
+
uploading: string;
|
|
38
|
+
success: string;
|
|
39
|
+
cancel: string;
|
|
40
|
+
retry: string;
|
|
41
|
+
}>;
|
|
42
|
+
export declare function validateUploadItemState(state: UploadItemState): void;
|
|
43
|
+
export declare function validateUploadItemDescriptor(descriptor: UploadItemDescriptor): void;
|
|
44
|
+
export declare function validateUploadItemLabels(labels: UploadItemLabels): void;
|
|
45
|
+
/** Rejects duplicate stable ids so a rendered list never desyncs a row's identity. */
|
|
46
|
+
export declare function validateUploadItemList(items: readonly UploadItemDescriptor[]): void;
|
|
47
|
+
export type UploadItemAction = "cancel" | "retry";
|
|
48
|
+
/**
|
|
49
|
+
* The only place cancel/retry availability is computed. Renderers must not
|
|
50
|
+
* keep their own copy of this rule — it would drift from `status` the first
|
|
51
|
+
* time a new state is added.
|
|
52
|
+
*/
|
|
53
|
+
export declare function getUploadItemAvailableAction(state: UploadItemState): UploadItemAction | null;
|
|
54
|
+
export type UploadItemAnnouncement = Readonly<{
|
|
55
|
+
/** Accessible name — the file name, kept independent of status (Statistic precedent). */
|
|
56
|
+
label: string;
|
|
57
|
+
/** Accessible description/live-region text. Never color-only. */
|
|
58
|
+
description: string;
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* Falls back to a rounded percent when a product supplies numeric `progress`
|
|
62
|
+
* but no `progressLabel` — the one place this module computes visible copy
|
|
63
|
+
* itself, mirroring Slider's raw-value fallback rather than Statistic's
|
|
64
|
+
* strict "product always formats" rule, because an uploading row with no
|
|
65
|
+
* numeric feedback at all is a worse default than a plain percentage.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveUploadItemAnnouncement(descriptor: UploadItemDescriptor, labels: UploadItemLabels): UploadItemAnnouncement;
|
|
68
|
+
export declare const uploadItemRecipe: {
|
|
69
|
+
readonly slots: readonly ["root", "icon", "name", "meta", "progress", "statusText", "cancel", "retry"];
|
|
70
|
+
readonly defaults: {
|
|
71
|
+
readonly size: "medium";
|
|
72
|
+
};
|
|
73
|
+
readonly row: {
|
|
74
|
+
readonly minHeight: 68;
|
|
75
|
+
readonly paddingHorizontal: 16;
|
|
76
|
+
readonly gap: 12;
|
|
77
|
+
readonly radius: "md";
|
|
78
|
+
};
|
|
79
|
+
readonly name: {
|
|
80
|
+
readonly color: Readonly<{
|
|
81
|
+
source: "theme";
|
|
82
|
+
key: "textBody";
|
|
83
|
+
alpha?: number;
|
|
84
|
+
}>;
|
|
85
|
+
readonly textVariant: "body";
|
|
86
|
+
};
|
|
87
|
+
readonly meta: {
|
|
88
|
+
readonly color: Readonly<{
|
|
89
|
+
source: "theme";
|
|
90
|
+
key: "textMuted";
|
|
91
|
+
alpha?: number;
|
|
92
|
+
}>;
|
|
93
|
+
readonly textVariant: "label";
|
|
94
|
+
};
|
|
95
|
+
readonly statusTones: {
|
|
96
|
+
readonly pending: Readonly<{
|
|
97
|
+
source: "theme";
|
|
98
|
+
key: "textMuted";
|
|
99
|
+
alpha?: number;
|
|
100
|
+
}>;
|
|
101
|
+
readonly uploading: Readonly<{
|
|
102
|
+
source: "theme";
|
|
103
|
+
key: "contentBrand";
|
|
104
|
+
alpha?: number;
|
|
105
|
+
}>;
|
|
106
|
+
readonly success: Readonly<{
|
|
107
|
+
source: "accent";
|
|
108
|
+
key: "success";
|
|
109
|
+
alpha?: number;
|
|
110
|
+
}>;
|
|
111
|
+
readonly error: Readonly<{
|
|
112
|
+
source: "theme";
|
|
113
|
+
key: "danger";
|
|
114
|
+
alpha?: number;
|
|
115
|
+
}>;
|
|
116
|
+
};
|
|
117
|
+
/** Reuses `progressRecipe` verbatim — no second progress bar. */
|
|
118
|
+
readonly progress: {
|
|
119
|
+
readonly size: "medium";
|
|
120
|
+
readonly tone: "brand";
|
|
121
|
+
readonly errorTone: "danger";
|
|
122
|
+
};
|
|
123
|
+
readonly action: {
|
|
124
|
+
readonly minTarget: 44;
|
|
125
|
+
readonly color: Readonly<{
|
|
126
|
+
source: "theme";
|
|
127
|
+
key: "contentBrand";
|
|
128
|
+
alpha?: number;
|
|
129
|
+
}>;
|
|
130
|
+
readonly dangerColor: Readonly<{
|
|
131
|
+
source: "theme";
|
|
132
|
+
key: "danger";
|
|
133
|
+
alpha?: number;
|
|
134
|
+
}>;
|
|
135
|
+
};
|
|
136
|
+
readonly states: {
|
|
137
|
+
readonly focus: {
|
|
138
|
+
readonly color: Readonly<{
|
|
139
|
+
source: "theme";
|
|
140
|
+
key: "contentBrand";
|
|
141
|
+
alpha?: number;
|
|
142
|
+
}>;
|
|
143
|
+
readonly width: 2;
|
|
144
|
+
readonly offset: 2;
|
|
145
|
+
};
|
|
146
|
+
readonly disabledOpacity: 0.5;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* `stateAxes.content` maps status onto the common axis
|
|
151
|
+
* (pending→idle, uploading→loading, success→complete, error→error).
|
|
152
|
+
* `loadingMore`/`empty` do not apply — one row is never paginated.
|
|
153
|
+
*/
|
|
154
|
+
export declare const uploadItemBehavior: {
|
|
155
|
+
readonly controlled: readonly [];
|
|
156
|
+
readonly inputs: readonly ["descriptor", "labels"];
|
|
157
|
+
readonly events: readonly ["onCancel", "onRetry"];
|
|
158
|
+
readonly stateAxes: {
|
|
159
|
+
readonly content: readonly ["idle", "loading", "complete", "error"];
|
|
160
|
+
};
|
|
161
|
+
readonly web: {
|
|
162
|
+
readonly roles: readonly ["group", "progressbar", "button"];
|
|
163
|
+
readonly keyboard: readonly ["Tab", "Enter", "Space"];
|
|
164
|
+
readonly focus: "native";
|
|
165
|
+
};
|
|
166
|
+
readonly native: {
|
|
167
|
+
readonly roles: readonly ["progressbar", "button"];
|
|
168
|
+
readonly states: readonly ["busy"];
|
|
169
|
+
readonly actions: readonly ["cancel", "retry"];
|
|
170
|
+
};
|
|
171
|
+
readonly scenarios: readonly ["progress-is-announced-as-a-sentence-not-only-a-filled-bar", "cancel-is-reachable-only-while-uploading", "retry-is-reachable-only-while-error", "pending-and-success-expose-no-destructive-or-retry-action", "indeterminate-progress-falls-back-to-a-static-uploading-label", "reuses-the-shared-progress-recipe-instead-of-a-new-bar", "duplicate-ids-in-a-rendered-list-are-rejected"];
|
|
172
|
+
};
|
|
173
|
+
//# sourceMappingURL=upload-item.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-item.d.ts","sourceRoot":"","sources":["../src/upload-item.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAE7E,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAErE,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,MAAM,EAAE,WAAW,CAAC;IACpB,qFAAqF;IACrF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAErE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,MAAM,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GACvB,sBAAsB,GACtB,wBAAwB,GACxB,sBAAsB,GACtB,oBAAoB,CAAC;AAEzB,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAQH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAoBpE;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,oBAAoB,GAAG,IAAI,CAKnF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAIvE;AAED,sFAAsF;AACtF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,GAAG,IAAI,CASnF;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAElD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,GAAG,IAAI,CAI5F;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,oBAAoB,EAChC,MAAM,EAAE,gBAAgB,GACvB,sBAAsB,CAmBxB;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAiB3B,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkClE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;CA0BM,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { focusIndicatorContract } from "./component-contracts.js";
|
|
2
|
+
import { progressRecipe, } from "./progress-recipe.js";
|
|
3
|
+
import { control, layout, opacity, radius, spacing } from "./foundations.js";
|
|
4
|
+
import { semanticColors } from "./semantic-colors.js";
|
|
5
|
+
function assertNonEmpty(value, field) {
|
|
6
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
7
|
+
throw new TypeError(`UploadItem ${field} must not be empty`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function validateUploadItemState(state) {
|
|
11
|
+
const status = state.status;
|
|
12
|
+
if (status !== "pending" &&
|
|
13
|
+
status !== "uploading" &&
|
|
14
|
+
status !== "success" &&
|
|
15
|
+
status !== "error") {
|
|
16
|
+
throw new TypeError(`Unsupported UploadItem status: ${String(status)}`);
|
|
17
|
+
}
|
|
18
|
+
if (status === "uploading") {
|
|
19
|
+
const { progress, progressLabel } = state;
|
|
20
|
+
if (progress !== null && (!Number.isFinite(progress) || progress < 0 || progress > 1)) {
|
|
21
|
+
throw new RangeError("UploadItem progress must be null or a number between 0 and 1");
|
|
22
|
+
}
|
|
23
|
+
if (progressLabel !== undefined)
|
|
24
|
+
assertNonEmpty(progressLabel, "progressLabel");
|
|
25
|
+
}
|
|
26
|
+
if (status === "error") {
|
|
27
|
+
assertNonEmpty(state.message, "error state message");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function validateUploadItemDescriptor(descriptor) {
|
|
31
|
+
assertNonEmpty(descriptor.id, "id");
|
|
32
|
+
assertNonEmpty(descriptor.name, "name");
|
|
33
|
+
if (descriptor.sizeLabel !== undefined)
|
|
34
|
+
assertNonEmpty(descriptor.sizeLabel, "sizeLabel");
|
|
35
|
+
validateUploadItemState(descriptor.state);
|
|
36
|
+
}
|
|
37
|
+
export function validateUploadItemLabels(labels) {
|
|
38
|
+
for (const field of ["pending", "uploading", "success", "cancel", "retry"]) {
|
|
39
|
+
assertNonEmpty(labels[field], `labels.${field}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Rejects duplicate stable ids so a rendered list never desyncs a row's identity. */
|
|
43
|
+
export function validateUploadItemList(items) {
|
|
44
|
+
const ids = new Set();
|
|
45
|
+
for (const item of items) {
|
|
46
|
+
validateUploadItemDescriptor(item);
|
|
47
|
+
if (ids.has(item.id)) {
|
|
48
|
+
throw new TypeError(`Duplicate UploadItem id: ${item.id}`);
|
|
49
|
+
}
|
|
50
|
+
ids.add(item.id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The only place cancel/retry availability is computed. Renderers must not
|
|
55
|
+
* keep their own copy of this rule — it would drift from `status` the first
|
|
56
|
+
* time a new state is added.
|
|
57
|
+
*/
|
|
58
|
+
export function getUploadItemAvailableAction(state) {
|
|
59
|
+
if (state.status === "uploading")
|
|
60
|
+
return "cancel";
|
|
61
|
+
if (state.status === "error")
|
|
62
|
+
return "retry";
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Falls back to a rounded percent when a product supplies numeric `progress`
|
|
67
|
+
* but no `progressLabel` — the one place this module computes visible copy
|
|
68
|
+
* itself, mirroring Slider's raw-value fallback rather than Statistic's
|
|
69
|
+
* strict "product always formats" rule, because an uploading row with no
|
|
70
|
+
* numeric feedback at all is a worse default than a plain percentage.
|
|
71
|
+
*/
|
|
72
|
+
export function resolveUploadItemAnnouncement(descriptor, labels) {
|
|
73
|
+
validateUploadItemDescriptor(descriptor);
|
|
74
|
+
validateUploadItemLabels(labels);
|
|
75
|
+
const { state } = descriptor;
|
|
76
|
+
if (state.status === "pending") {
|
|
77
|
+
return { label: descriptor.name, description: labels.pending };
|
|
78
|
+
}
|
|
79
|
+
if (state.status === "uploading") {
|
|
80
|
+
if (state.progressLabel) {
|
|
81
|
+
return { label: descriptor.name, description: state.progressLabel };
|
|
82
|
+
}
|
|
83
|
+
const description = state.progress === null ? labels.uploading : `${Math.round(state.progress * 100)}%`;
|
|
84
|
+
return { label: descriptor.name, description };
|
|
85
|
+
}
|
|
86
|
+
if (state.status === "success") {
|
|
87
|
+
return { label: descriptor.name, description: labels.success };
|
|
88
|
+
}
|
|
89
|
+
return { label: descriptor.name, description: state.message };
|
|
90
|
+
}
|
|
91
|
+
export const uploadItemRecipe = {
|
|
92
|
+
slots: ["root", "icon", "name", "meta", "progress", "statusText", "cancel", "retry"],
|
|
93
|
+
defaults: { size: "medium" },
|
|
94
|
+
row: {
|
|
95
|
+
minHeight: layout.rowHeight.twoLine,
|
|
96
|
+
paddingHorizontal: spacing.md,
|
|
97
|
+
gap: spacing.sm,
|
|
98
|
+
radius: "md",
|
|
99
|
+
},
|
|
100
|
+
name: { color: semanticColors.content.body, textVariant: "body" },
|
|
101
|
+
meta: { color: semanticColors.content.secondary, textVariant: "label" },
|
|
102
|
+
statusTones: {
|
|
103
|
+
pending: semanticColors.content.secondary,
|
|
104
|
+
uploading: semanticColors.content.brand,
|
|
105
|
+
success: semanticColors.feedback.success.foreground,
|
|
106
|
+
error: semanticColors.content.danger,
|
|
107
|
+
},
|
|
108
|
+
/** Reuses `progressRecipe` verbatim — no second progress bar. */
|
|
109
|
+
progress: {
|
|
110
|
+
size: progressRecipe.defaults.size,
|
|
111
|
+
tone: progressRecipe.defaults.tone,
|
|
112
|
+
errorTone: "danger",
|
|
113
|
+
},
|
|
114
|
+
action: {
|
|
115
|
+
minTarget: control.minTouchTarget,
|
|
116
|
+
color: semanticColors.content.brand,
|
|
117
|
+
dangerColor: semanticColors.content.danger,
|
|
118
|
+
},
|
|
119
|
+
states: {
|
|
120
|
+
focus: focusIndicatorContract,
|
|
121
|
+
disabledOpacity: opacity.disabled,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* `stateAxes.content` maps status onto the common axis
|
|
126
|
+
* (pending→idle, uploading→loading, success→complete, error→error).
|
|
127
|
+
* `loadingMore`/`empty` do not apply — one row is never paginated.
|
|
128
|
+
*/
|
|
129
|
+
export const uploadItemBehavior = {
|
|
130
|
+
controlled: [],
|
|
131
|
+
inputs: ["descriptor", "labels"],
|
|
132
|
+
events: ["onCancel", "onRetry"],
|
|
133
|
+
stateAxes: {
|
|
134
|
+
content: ["idle", "loading", "complete", "error"],
|
|
135
|
+
},
|
|
136
|
+
web: {
|
|
137
|
+
roles: ["group", "progressbar", "button"],
|
|
138
|
+
keyboard: ["Tab", "Enter", "Space"],
|
|
139
|
+
focus: "native",
|
|
140
|
+
},
|
|
141
|
+
native: {
|
|
142
|
+
roles: ["progressbar", "button"],
|
|
143
|
+
states: ["busy"],
|
|
144
|
+
actions: ["cancel", "retry"],
|
|
145
|
+
},
|
|
146
|
+
scenarios: [
|
|
147
|
+
"progress-is-announced-as-a-sentence-not-only-a-filled-bar",
|
|
148
|
+
"cancel-is-reachable-only-while-uploading",
|
|
149
|
+
"retry-is-reachable-only-while-error",
|
|
150
|
+
"pending-and-success-expose-no-destructive-or-retry-action",
|
|
151
|
+
"indeterminate-progress-falls-back-to-a-static-uploading-label",
|
|
152
|
+
"reuses-the-shared-progress-recipe-instead-of-a-new-bar",
|
|
153
|
+
"duplicate-ids-in-a-rendered-list-are-rejected",
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=upload-item.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-item.js","sourceRoot":"","sources":["../src/upload-item.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { BehaviorContract } from \"./behaviors.js\";\nimport { focusIndicatorContract } from \"./component-contracts.js\";\nimport {\n progressRecipe,\n type ProgressSize,\n type ProgressTone,\n} from \"./progress-recipe.js\";\nimport { control, layout, opacity, radius, spacing, type TextVariant } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\nexport type UploadItemStatus = \"pending\" | \"uploading\" | \"success\" | \"error\";\n\nexport type UploadItemPendingState = Readonly<{ status: \"pending\" }>;\n\nexport type UploadItemUploadingState = Readonly<{\n status: \"uploading\";\n /** Product-measured transfer fraction. `null` means indeterminate — size unknown. */\n progress: number | null;\n /** Product-formatted announcement, e.g. \"1.6MB / 3.2MB 업로드 중\". */\n progressLabel?: string;\n}>;\n\nexport type UploadItemSuccessState = Readonly<{ status: \"success\" }>;\n\nexport type UploadItemErrorState = Readonly<{\n status: \"error\";\n /** States the problem and the next action, e.g. \"네트워크 오류입니다. 다시 시도해주세요.\" */\n message: string;\n}>;\n\n/**\n * Cancel and retry are never stored as separate booleans: deriving them from\n * `status` (see `getUploadItemAvailableAction`) makes \"cancel while error\" or\n * \"retry while uploading\" unrepresentable, the same way `SheetOpenState` and\n * `LoadMoreState` close off invalid combinations by construction.\n */\nexport type UploadItemState =\n | UploadItemPendingState\n | UploadItemUploadingState\n | UploadItemSuccessState\n | UploadItemErrorState;\n\nexport type UploadItemDescriptor = Readonly<{\n id: string;\n name: string;\n /** Product-formatted size, e.g. \"1.2 MB\" — HJM does not format bytes. */\n sizeLabel?: string;\n state: UploadItemState;\n}>;\n\nexport type UploadItemLabels = Readonly<{\n pending: string;\n /** Fallback when `progress` is `null` or `progressLabel` is absent. */\n uploading: string;\n success: string;\n cancel: string;\n retry: string;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`UploadItem ${field} must not be empty`);\n }\n}\n\nexport function validateUploadItemState(state: UploadItemState): void {\n const status = (state as Readonly<{ status?: unknown }>).status;\n if (\n status !== \"pending\" &&\n status !== \"uploading\" &&\n status !== \"success\" &&\n status !== \"error\"\n ) {\n throw new TypeError(`Unsupported UploadItem status: ${String(status)}`);\n }\n if (status === \"uploading\") {\n const { progress, progressLabel } = state as UploadItemUploadingState;\n if (progress !== null && (!Number.isFinite(progress) || progress < 0 || progress > 1)) {\n throw new RangeError(\"UploadItem progress must be null or a number between 0 and 1\");\n }\n if (progressLabel !== undefined) assertNonEmpty(progressLabel, \"progressLabel\");\n }\n if (status === \"error\") {\n assertNonEmpty((state as UploadItemErrorState).message, \"error state message\");\n }\n}\n\nexport function validateUploadItemDescriptor(descriptor: UploadItemDescriptor): void {\n assertNonEmpty(descriptor.id, \"id\");\n assertNonEmpty(descriptor.name, \"name\");\n if (descriptor.sizeLabel !== undefined) assertNonEmpty(descriptor.sizeLabel, \"sizeLabel\");\n validateUploadItemState(descriptor.state);\n}\n\nexport function validateUploadItemLabels(labels: UploadItemLabels): void {\n for (const field of [\"pending\", \"uploading\", \"success\", \"cancel\", \"retry\"] as const) {\n assertNonEmpty(labels[field], `labels.${field}`);\n }\n}\n\n/** Rejects duplicate stable ids so a rendered list never desyncs a row's identity. */\nexport function validateUploadItemList(items: readonly UploadItemDescriptor[]): void {\n const ids = new Set<string>();\n for (const item of items) {\n validateUploadItemDescriptor(item);\n if (ids.has(item.id)) {\n throw new TypeError(`Duplicate UploadItem id: ${item.id}`);\n }\n ids.add(item.id);\n }\n}\n\nexport type UploadItemAction = \"cancel\" | \"retry\";\n\n/**\n * The only place cancel/retry availability is computed. Renderers must not\n * keep their own copy of this rule — it would drift from `status` the first\n * time a new state is added.\n */\nexport function getUploadItemAvailableAction(state: UploadItemState): UploadItemAction | null {\n if (state.status === \"uploading\") return \"cancel\";\n if (state.status === \"error\") return \"retry\";\n return null;\n}\n\nexport type UploadItemAnnouncement = Readonly<{\n /** Accessible name — the file name, kept independent of status (Statistic precedent). */\n label: string;\n /** Accessible description/live-region text. Never color-only. */\n description: string;\n}>;\n\n/**\n * Falls back to a rounded percent when a product supplies numeric `progress`\n * but no `progressLabel` — the one place this module computes visible copy\n * itself, mirroring Slider's raw-value fallback rather than Statistic's\n * strict \"product always formats\" rule, because an uploading row with no\n * numeric feedback at all is a worse default than a plain percentage.\n */\nexport function resolveUploadItemAnnouncement(\n descriptor: UploadItemDescriptor,\n labels: UploadItemLabels,\n): UploadItemAnnouncement {\n validateUploadItemDescriptor(descriptor);\n validateUploadItemLabels(labels);\n const { state } = descriptor;\n if (state.status === \"pending\") {\n return { label: descriptor.name, description: labels.pending };\n }\n if (state.status === \"uploading\") {\n if (state.progressLabel) {\n return { label: descriptor.name, description: state.progressLabel };\n }\n const description =\n state.progress === null ? labels.uploading : `${Math.round(state.progress * 100)}%`;\n return { label: descriptor.name, description };\n }\n if (state.status === \"success\") {\n return { label: descriptor.name, description: labels.success };\n }\n return { label: descriptor.name, description: state.message };\n}\n\nexport const uploadItemRecipe = {\n slots: [\"root\", \"icon\", \"name\", \"meta\", \"progress\", \"statusText\", \"cancel\", \"retry\"] as const,\n defaults: { size: \"medium\" },\n row: {\n minHeight: layout.rowHeight.twoLine,\n paddingHorizontal: spacing.md,\n gap: spacing.sm,\n radius: \"md\",\n },\n name: { color: semanticColors.content.body, textVariant: \"body\" },\n meta: { color: semanticColors.content.secondary, textVariant: \"label\" },\n statusTones: {\n pending: semanticColors.content.secondary,\n uploading: semanticColors.content.brand,\n success: semanticColors.feedback.success.foreground,\n error: semanticColors.content.danger,\n },\n /** Reuses `progressRecipe` verbatim — no second progress bar. */\n progress: {\n size: progressRecipe.defaults.size,\n tone: progressRecipe.defaults.tone,\n errorTone: \"danger\",\n },\n action: {\n minTarget: control.minTouchTarget,\n color: semanticColors.content.brand,\n dangerColor: semanticColors.content.danger,\n },\n states: {\n focus: focusIndicatorContract,\n disabledOpacity: opacity.disabled,\n },\n} as const satisfies {\n slots: readonly [\n \"root\",\n \"icon\",\n \"name\",\n \"meta\",\n \"progress\",\n \"statusText\",\n \"cancel\",\n \"retry\",\n ];\n defaults: { size: \"medium\" };\n row: { minHeight: number; paddingHorizontal: number; gap: number; radius: keyof typeof radius };\n name: { color: ColorReference; textVariant: TextVariant };\n meta: { color: ColorReference; textVariant: TextVariant };\n statusTones: Record<UploadItemStatus, ColorReference>;\n progress: { size: ProgressSize; tone: ProgressTone; errorTone: ProgressTone };\n action: { minTarget: number; color: ColorReference; dangerColor: ColorReference };\n states: { focus: typeof focusIndicatorContract; disabledOpacity: number };\n};\n\n/**\n * `stateAxes.content` maps status onto the common axis\n * (pending→idle, uploading→loading, success→complete, error→error).\n * `loadingMore`/`empty` do not apply — one row is never paginated.\n */\nexport const uploadItemBehavior = {\n controlled: [],\n inputs: [\"descriptor\", \"labels\"],\n events: [\"onCancel\", \"onRetry\"],\n stateAxes: {\n content: [\"idle\", \"loading\", \"complete\", \"error\"],\n },\n web: {\n roles: [\"group\", \"progressbar\", \"button\"],\n keyboard: [\"Tab\", \"Enter\", \"Space\"],\n focus: \"native\",\n },\n native: {\n roles: [\"progressbar\", \"button\"],\n states: [\"busy\"],\n actions: [\"cancel\", \"retry\"],\n },\n scenarios: [\n \"progress-is-announced-as-a-sentence-not-only-a-filled-bar\",\n \"cancel-is-reachable-only-while-uploading\",\n \"retry-is-reachable-only-while-error\",\n \"pending-and-success-expose-no-destructive-or-retry-action\",\n \"indeterminate-progress-falls-back-to-a-static-uploading-label\",\n \"reuses-the-shared-progress-recipe-instead-of-a-new-bar\",\n \"duplicate-ids-in-a-rendered-list-are-rejected\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,EAAG,OAAgB,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +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 = \"0.8.2\" as const;\n"]}
|
package/docs/affix.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Affix — 계약을 만들지 않는다
|
|
2
|
+
|
|
3
|
+
## 문제로 제기된 것
|
|
4
|
+
|
|
5
|
+
Ant Design `Affix`는 스크롤이 특정 지점을 지나면 임의 요소를 화면에 고정합니다. 검토
|
|
6
|
+
관찰은 두 가지였습니다 — `TopBar`가 이미 있고(`src/layout.ts:9`, RN `native`, beta), Web
|
|
7
|
+
CSS `position: sticky`가 대부분을 푼다면, 이것이 컴포넌트인지 아니면 레이아웃 기법인지
|
|
8
|
+
확인하라는 것과, `docs/expansion-roadmap.md`의 `Stack` 판정("반복되는 내부 flex를 감싸는
|
|
9
|
+
것만으로 제품 의미나 접근성 계약이 생기지 않는다")이 그대로 적용될 가능성이 높다는
|
|
10
|
+
것이었습니다.
|
|
11
|
+
|
|
12
|
+
## 판정: 만들지 않는다
|
|
13
|
+
|
|
14
|
+
### 1. 관찰된 모든 실사용은 이미 다른 컴포넌트나 CSS 한 줄이 소유한다
|
|
15
|
+
|
|
16
|
+
- **BurnTok** 웹 헤더(`apps/web/src/app/page.tsx:118`, `messages/page.tsx:27`,
|
|
17
|
+
`u/[id]/ProfileClient.tsx:80`, `c/[id]/StoryClient.tsx:287`,
|
|
18
|
+
`notifications/page.tsx:44`)는 모두 `<header className="sticky top-0 ...">`로 상단바를
|
|
19
|
+
고정합니다. 이는 정확히 `TopBar`의 자리이고, `position: sticky` 한 줄로 끝나는
|
|
20
|
+
문제입니다 — 스크롤 진행률 계산, 고정 해제 조건 분기, 별도 controlled 상태 없이 CSS가
|
|
21
|
+
전부 해결합니다.
|
|
22
|
+
- **Yajalal RN**에도 이미 `TopBar` 구현(`modules/app-rn/src/components/ui/AppTopBar.tsx`)이
|
|
23
|
+
있습니다. Native는애초에 헤더가 화면 최상단에 고정 마운트되는 것이 기본값이라
|
|
24
|
+
"스크롤 임계값을 지나면 고정"이라는 Affix 특유의 조건 자체가 성립하지 않습니다.
|
|
25
|
+
- 그 외 "고정" 관련 코드는 각 컴포넌트가 이미 소유한 문제입니다: `StatTable`의 첫 열
|
|
26
|
+
고정(`stickyFirstColumn`, `stat-table-contract.ts:23`)은 표 내부 anatomy이고,
|
|
27
|
+
`BottomCTA`의 하단 고정 영역(`AppBottomCTA.tsx:39` "Safe-area-aware sticky action
|
|
28
|
+
area")은 이미 별도 컴포넌트로 계약돼 있습니다. 둘 다 "임의 요소를 고정하는 범용
|
|
29
|
+
wrapper"를 필요로 하지 않습니다.
|
|
30
|
+
|
|
31
|
+
### 2. 임의 콘텐츠를 고정하는 범용 요구 자체가 관측되지 않는다
|
|
32
|
+
|
|
33
|
+
antd `Affix`의 핵심 사용법 — 헤더도 테이블도 아닌 **임의 콘텐츠**(플로팅 목차, 사이드
|
|
34
|
+
액션 카드 등)를 스크롤 중간에 고정 — 는 두 제품 어디에도 없습니다. 있는 모든 "고정"
|
|
35
|
+
사례는 위처럼 헤더(TopBar) 아니면 특정 컴포넌트 내부 anatomy(StatTable, BottomCTA)로
|
|
36
|
+
이미 이름이 있습니다.
|
|
37
|
+
|
|
38
|
+
### 3. 계약할 상태 축이 없다
|
|
39
|
+
|
|
40
|
+
Affix가 컴포넌트가 되려면 "Web과 Native가 각자 다른 기법으로 같은 결과를 낸다"는 공유
|
|
41
|
+
semantic이 있어야 합니다(`docs/architecture.md`의 `adaptive` 정의). 그런데 Web은 CSS
|
|
42
|
+
`position: sticky`(레이아웃 엔진이 처리, JS 상태 없음), Native는 애초에 스크롤에 따라
|
|
43
|
+
고정/해제되는 개념 자체가 드뭅니다(헤더는 기본 고정, 그 외에는 `stickyHeaderIndices`처럼
|
|
44
|
+
리스트 컴포넌트가 소유). 이는 `docs/virtual-list.md`가 항목 높이 추정을 판정한 것과 같은
|
|
45
|
+
모양입니다 — 이름도 단위도 의미도 대응하지 않는 렌더러 힌트일 뿐, 공유할 사용자 의미가
|
|
46
|
+
없습니다.
|
|
47
|
+
|
|
48
|
+
`Stack`의 판정과 같은 자리입니다: 반복되는 CSS 속성 하나를 감싸는 것만으로 제품 의미나
|
|
49
|
+
접근성 계약이 생기지 않습니다.
|
|
50
|
+
|
|
51
|
+
## 만들지 않은 것
|
|
52
|
+
|
|
53
|
+
`src/affix.ts`, `test/affix.test.ts`는 없습니다. `componentCatalog`의
|
|
54
|
+
`{ name: "Affix", category: "utility", platform: "web", status: "planned" }` 행과
|
|
55
|
+
crosswalk의 `Affix → Affix` direct 관계(`src/component-references.ts:125`)는 건드리지
|
|
56
|
+
않습니다.
|
|
57
|
+
|
|
58
|
+
## 뒤집힐 조건
|
|
59
|
+
|
|
60
|
+
1. 헤더도 테이블도 CTA도 아닌 **임의 콘텐츠**를 스크롤 중간 지점부터 고정해야 하는 실제
|
|
61
|
+
화면(예: 긴 폼 옆의 플로팅 요약 카드)이 두 제품 중 하나에 나타난다.
|
|
62
|
+
2. 그 화면에서 CSS `position: sticky`만으로 부족한 요구(예: 여러 sticky 요소의 겹침
|
|
63
|
+
순서 조정, 고정 해제 시점의 접근성 발표)가 측정된다.
|
package/docs/anchor.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Anchor — 계약을 만들지 않는다
|
|
2
|
+
|
|
3
|
+
## 문제로 제기된 것
|
|
4
|
+
|
|
5
|
+
Ant Design `Anchor`는 긴 문서 안의 목차 링크를 스크롤 위치와 동기화합니다. 검토
|
|
6
|
+
관찰은, 진짜 계약은 **현재 위치 표시**(`aria-current`), **스크롤 동기화**,
|
|
7
|
+
**reduced motion에서 부드러운 스크롤을 끄는 것**이라는 것과, 두 제품에 긴 문서 화면이
|
|
8
|
+
있는지 확인하라는 것이었습니다.
|
|
9
|
+
|
|
10
|
+
## 판정: 만들지 않는다
|
|
11
|
+
|
|
12
|
+
### 1. 두 제품 중 어디에도 목차가 필요한 긴 단일 문서 화면이 없다
|
|
13
|
+
|
|
14
|
+
- **Yajalal RN**: `약관`/`개인정보처리방침`/`이용약관` 류 화면, 또는 섹션이 여럿인 긴
|
|
15
|
+
스크롤 문서 화면을 전수 검색했지만 찾지 못했습니다. `modules/app-rn/src/lib/copy/terms.ts`는
|
|
16
|
+
이름과 달리 법적 약관이 아니라 "같은 개념에는 하나의 단어"를 강제하는 **카피 용어
|
|
17
|
+
단일 출처**(구단·경기차 표기 등)일 뿐, 목차 내비게이션이 필요한 문서가 아닙니다.
|
|
18
|
+
화면 구조도 DESIGN_SYSTEM.md가 "48pt 상단바 + overline·대형 제목·설명 문장으로 이루어진
|
|
19
|
+
페이지 헤더는 쓰지 않는다"로 규정해, 애초에 스크롤 목차가 붙는 긴 article 레이아웃을
|
|
20
|
+
피하는 방향입니다.
|
|
21
|
+
- **BurnTok**: `apps/web/src/app`의 최상위 라우트(`feed`, `messages`, `notifications`,
|
|
22
|
+
`u/[id]`, `c/[id]`, `create`, `ideas`, `run` 등)를 전수 확인했지만 약관·도움말·블로그
|
|
23
|
+
같은 긴 문서 페이지 자체가 없습니다. 앱의 성격(피드형 소셜/영상)상 스크롤 목차가
|
|
24
|
+
붙을 만한 단일 긴 문서 화면이 없습니다.
|
|
25
|
+
- `anchor`로 두 저장소를 검색했을 때 나온 유일한 실제 코드 매치는 BurnTok
|
|
26
|
+
`AppTooltip.tsx`/`anchored-overlay.ts`의 "anchor element"였는데, 이는 툴팁이 붙는
|
|
27
|
+
기준 요소를 뜻하는 **오버레이 위치 계산 용어**로, antd `Anchor`(문서 내 목차)와는
|
|
28
|
+
이름만 같고 완전히 다른 개념입니다.
|
|
29
|
+
|
|
30
|
+
### 2. 계약할 상태 축은 유효하지만, 지금 채울 화면이 없다
|
|
31
|
+
|
|
32
|
+
관찰이 짚은 세 계약(`aria-current` 현재 위치, 스크롤 동기화, reduced motion에서 smooth
|
|
33
|
+
scroll 끄기)은 실제로 새로운 플랫폼 중립 개념입니다 — `BottomNavigation`의
|
|
34
|
+
`aria-current="page"`(`docs/architecture.md` BottomNavigation 절)와 다른 종류의 "현재
|
|
35
|
+
위치" 표시이고, `Toast`/`Sheet`의 Reduce Motion 처리와도 다른 대상(스크롤 애니메이션)에
|
|
36
|
+
적용됩니다. `Notification`/`Dropdown`/`VirtualList`처럼 "문제가 이미 다른 컴포넌트에
|
|
37
|
+
흡수됐다"는 판정은 아닙니다 — 흡수할 곳이 없고, 처음부터 이 문제를 푸는 컴포넌트가
|
|
38
|
+
없다는 뜻입니다.
|
|
39
|
+
|
|
40
|
+
그럼에도 만들지 않는 이유는 순전히 **측정된 수요 부재**입니다. 목차와 동기화할 긴
|
|
41
|
+
문서 자체가 없는 상태에서 스크롤 동기화 로직을 먼저 설계하면, 실제 문서 구조(섹션
|
|
42
|
+
개수, 중첩 깊이, 모바일에서 세로 목차를 어떻게 접을지)를 모른 채 API를 고정하게 됩니다.
|
|
43
|
+
`docs/authoring-brief.md`와 로드맵이 반복해서 요구하는 "실제 제품 vertical slice 없이
|
|
44
|
+
승격하지 않는다"는 gate 이전에, **애초에 계약을 검증할 화면이 없는** 상태입니다.
|
|
45
|
+
|
|
46
|
+
## 만들지 않은 것
|
|
47
|
+
|
|
48
|
+
`src/anchor.ts`, `test/anchor.test.ts`는 없습니다. `componentCatalog`의
|
|
49
|
+
`{ name: "Anchor", category: "navigation", platform: "web", status: "planned" }` 행과
|
|
50
|
+
crosswalk의 `Anchor → Anchor` direct 관계(`src/component-references.ts:64`)는 건드리지
|
|
51
|
+
않습니다.
|
|
52
|
+
|
|
53
|
+
## 뒤집힐 조건
|
|
54
|
+
|
|
55
|
+
1. BurnTok 또는 Yajalal(Web 우선, `platform: "web"` 그대로 유지 가능)에 여러 섹션을 가진
|
|
56
|
+
긴 단일 문서 화면(약관, 도움말, 가이드 등)이 실제로 생긴다.
|
|
57
|
+
2. 그 화면에서 `aria-current` 기반 현재 섹션 표시, 스크롤 동기화, reduced motion의 즉시
|
|
58
|
+
이동(auto) 전환이 실제로 요구되는 vertical slice가 측정된다 — 그때 이 세 축을 공개
|
|
59
|
+
상태 축으로 계약한다.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Ant Design Coverage Benchmark
|
|
2
|
+
|
|
3
|
+
HJM은 Ant Design을 런타임 의존성이나 호환 API로 사용하지 않습니다. 넓은 컴포넌트 범위를
|
|
4
|
+
놓치지 않기 위한 **reference inventory**로만 사용하며, 각 문제를 HJM의 플랫폼 중립 의미,
|
|
5
|
+
접근성 계약, Web/Native renderer 경계로 다시 번역합니다.
|
|
6
|
+
|
|
7
|
+
## 고정 기준
|
|
8
|
+
|
|
9
|
+
- source: [Ant Design Components Overview](https://ant.design/components/overview/)
|
|
10
|
+
- captured: 2026-08-20
|
|
11
|
+
- version: 6.6.1
|
|
12
|
+
- core scope: 73 entries
|
|
13
|
+
- category counts: General 4, Layout 7, Navigation 7, Data Entry 18,
|
|
14
|
+
Data Display 21, Feedback 11, Other 5
|
|
15
|
+
|
|
16
|
+
별도 패키지인 Pro Components는 core 73개에 포함하지 않습니다. 이후 page pattern benchmark로
|
|
17
|
+
추가하더라도 별도의 inventory로 관리합니다.
|
|
18
|
+
|
|
19
|
+
## 기계 판독 가능한 crosswalk
|
|
20
|
+
|
|
21
|
+
`src/component-references.ts`의 `antDesignReferenceComponents`가 73개 source entry와 rename에
|
|
22
|
+
영향받지 않는 HJM canonical `ComponentId` target을 연결합니다.
|
|
23
|
+
|
|
24
|
+
- `direct`: 같은 사용자 문제를 거의 같은 component boundary로 해결
|
|
25
|
+
- `adapted`: 같은 문제를 HJM의 명명·플랫폼 의미로 번역
|
|
26
|
+
- `decomposed`: 하나의 Web-first component를 여러 HJM 계약으로 분리
|
|
27
|
+
|
|
28
|
+
예를 들어 Ant Design `Input`은 `Field`, `SearchField`, `TextArea`, `PasswordField`,
|
|
29
|
+
`OtpField`로 분해하고, `Drawer`는 adaptive `Sheet`와 Web `SidePanel`로 분해합니다. `Message`와 `Notification`은 **둘 다** HJM `Toast`로,
|
|
30
|
+
`Spin`은 `Spinner`, `Table`은 `DataTable`로 연결합니다.
|
|
31
|
+
|
|
32
|
+
`Notification`이 별도 target을 갖지 않는 이유는 `docs/notification.md`에 있습니다 — antd가
|
|
33
|
+
message와 notification을 가르는 축(정보 층, 지속 시간, 동시 개수, 위치, 긴급도)이 전부
|
|
34
|
+
이미 Toast의 **설정 값**이라, 새 상태 축도 새 접근성 개념도 생기지 않았습니다. 그래서
|
|
35
|
+
`Toast`의 alias로 흡수하고 catalog 행을 따로 두지 않습니다. 그 문서에는 이 판정이 뒤집힐
|
|
36
|
+
조건(화면 밖 push, 별도 알림함 이력처럼 Toast로 표현할 수 없는 요구)도 함께 적혀 있습니다.
|
|
37
|
+
|
|
38
|
+
`Dropdown`도 같은 판정으로 `Menu`에 흡수했습니다(`docs/dropdown.md`). antd는 Dropdown을
|
|
39
|
+
"트리거에 붙는 오버레이", Menu를 "그 안의 항목 목록"으로 가르지만 HJM `Menu`는 트리거·표면·
|
|
40
|
+
항목을 **모두 소유**합니다. "임의 콘텐츠를 담는 표면"으로 재해석하는 길도 검토했으나 그
|
|
41
|
+
문제는 `Popover`가 이미 점유하고 있어, 측정된 수요 없이 겹치는 두 표면을 만들지 않았습니다.
|
|
42
|
+
|
|
43
|
+
여러 source entry가 한 target을 가리키는 것은 허용됩니다 — 73개 추적은 **antd의 범위를
|
|
44
|
+
빠짐없이 보는 것**이 목적이지 1:1 대응을 만드는 것이 아닙니다. 반대로 **HJM에 새 컴포넌트를
|
|
45
|
+
만들지 않기로 한 판정도 산출물**입니다. 그 이유와 **판정이 뒤집힐 조건**을 문서로 남기면,
|
|
46
|
+
다음 사람이 같은 질문을 처음부터 다시 하지 않습니다. 이는 import나 prop 호환성을 의미하지 않습니다.
|
|
47
|
+
|
|
48
|
+
### 「만들지 않는다」에는 네 종류가 있다
|
|
49
|
+
|
|
50
|
+
판정 문서가 늘어나면서, 같은 결론이 서로 다른 이유에서 나온다는 것이 드러났습니다. 결론이
|
|
51
|
+
같아도 **다음 사람이 재검토할 조건이 다르므로** 문서에서 구별합니다.
|
|
52
|
+
|
|
53
|
+
| 종류 | 뜻 | 예 | 재검토 신호 |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| **흡수됨** | 그 문제를 이미 다른 컴포넌트가 완결한다 | `Notification`→Toast, `Dropdown`→Menu, `ContextPanel`→SidePanel/Sheet, `Flex`→Stack, `TimePicker`→Select 조합, `Rating`→Slider/Statistic | 흡수한 쪽이 못 푸는 요구가 나올 때 |
|
|
56
|
+
| **검증할 화면이 없음** | 계약 자체는 유효하나 이를 확인할 제품 화면이 없다 | `Anchor`, `Calendar` | 그 화면이 실제로 생길 때 |
|
|
57
|
+
| **거절됨** | 계약도 유효하고 화면이 생겨도 만들지 않는다 | `BorderBeam`(장식), `AppProvider`(런타임뿐), `Utility`(실체 없음) | 정체성이나 아키텍처 경계가 바뀔 때만 |
|
|
58
|
+
| **흡수 대기** | 흡수 판정은 끝났으나 **선결 축이 아직 없다** | `Cascader`(TreeSelect에 `valueMode`/`commitAt`가 추가돼야 성립) | 그 축이 실제로 추가될 때 |
|
|
59
|
+
|
|
60
|
+
둘을 섞으면 안 됩니다. **흡수됨**은 설계 판단이라 제품이 늘어나도 웬만해선 뒤집히지 않지만,
|
|
61
|
+
**검증할 화면이 없음**은 사실 관찰이라 **커밋 하나로 낡습니다**. 실제로 그런 일이 있었습니다
|
|
62
|
+
(`docs/expansion-roadmap.md`의 「vertical slice가 없는 계약」).
|
|
63
|
+
|
|
64
|
+
**거절됨**은 넷 중 가장 안정적입니다 — 제품이 아무리 늘어나도 뒤집히지 않고, 정체성이나 계층 경계가 바뀔 때만 재검토합니다.
|
|
65
|
+
|
|
66
|
+
catalog 행 처리도 여기서 갈립니다. **흡수됨**은 행을 지우고 흡수한 쪽에 `aliases`로 이름을
|
|
67
|
+
남깁니다 — 남겨 두면 "아직 만들 계획"으로 잘못 읽힙니다. **검증할 화면이 없음**은 행을
|
|
68
|
+
그대로 둡니다. 계약은 유효하고 언젠가 채워질 자리이기 때문입니다. **거절됨**은 행을 지울 수 없습니다 — crosswalk의 `targets`가 가리키고 있고 흡수할 다른 이름이 없기 때문입니다. 대신 `ComponentCatalogEntry.declinedReason`에 사유를 적습니다.
|
|
69
|
+
|
|
70
|
+
**흡수됨은 다시 두 갈래입니다.** 흡수한 대상이 **정확히 하나**면(`Notification`→Toast,
|
|
71
|
+
`Dropdown`→Menu, `Flex`·`Space`→Stack) 행을 지우고 그 하나에 `aliases`로 이름을 남깁니다.
|
|
72
|
+
대상이 **둘 이상의 조합**이면(`TimePicker`→Select 둘, `Rating`→Slider/Statistic,
|
|
73
|
+
`ConfirmPopover`→Popover/AlertDialog) alias를 걸 단일 이름이 없으므로 **행을 그대로 둡니다.**
|
|
74
|
+
이 저장소는 이 답을 세 번 반복해서 냈고, 우연이 아니라 규칙입니다.
|
|
75
|
+
|
|
76
|
+
**흡수 대기**는 행과 crosswalk를 **둘 다 건드리지 않습니다.** 흡수를 지금 적용하면 아직 만들지
|
|
77
|
+
않은 해결책을 완료로 표시하게 됩니다 — `planned`이 거짓말하던 문제와 방향만 반대인 같은 문제입니다.
|
|
78
|
+
결과는 「검증할 화면이 없음」과 같지만 재검토 신호가 다릅니다: 제품 화면이 아니라 **엔지니어링
|
|
79
|
+
선행 조건**이 신호입니다. `status`는 구현 성숙도 축이고 "만들 것인가"는 다른 질문이라 상태값을 늘리지 않고 직교 필드를 두었습니다 — 그 결정의 영향 범위 비교는 `docs/catalog-decision-status.md`에 있습니다.
|
|
80
|
+
|
|
81
|
+
## 범위와 구현을 분리한다
|
|
82
|
+
|
|
83
|
+
crosswalk의 target이 catalog에 존재하면 `tracked`입니다. 아래 분포는 target의
|
|
84
|
+
`stable | beta | planned` **maturity만** 계산하며 renderer 존재 여부는 별도의 Showcase
|
|
85
|
+
evidence registry로 판단합니다.
|
|
86
|
+
|
|
87
|
+
- fully mature: 모든 target이 stable 또는 beta
|
|
88
|
+
- partial maturity: decomposed target 중 일부만 stable 또는 beta
|
|
89
|
+
- planned only: 모든 target이 planned
|
|
90
|
+
|
|
91
|
+
2026-08-27 snapshot의 status 기반 분포는 **fully mature 45 / partial maturity 1 /
|
|
92
|
+
planned only 27**입니다. 따라서 73/73 tracking은 73개 구현 완료를 의미하지 않습니다.
|
|
93
|
+
홈과 Component Explorer는 이 수치를 분리해 표시합니다. 이 숫자는 source inventory 수가
|
|
94
|
+
아니라 HJM target의 maturity에서 계산하므로 catalog status가 바뀌면 함께 갱신합니다.
|
|
95
|
+
|
|
96
|
+
## lifecycle
|
|
97
|
+
|
|
98
|
+
Ant Design 6.6.1은 기존 `List`를 deprecated로 표시하고 `Listy`를 successor로 추가했습니다.
|
|
99
|
+
reference inventory는 `List.lifecycle = deprecated`, `Listy.lifecycle = new`로 보존하지만 HJM
|
|
100
|
+
`List`를 자동으로 deprecated 처리하지 않습니다. HJM은 기존 비가상 `List`와 planned
|
|
101
|
+
`VirtualList`를 서로 다른 사용 문제로 유지합니다.
|
|
102
|
+
|
|
103
|
+
## 업데이트 규칙
|
|
104
|
+
|
|
105
|
+
Ant Design 기준 버전을 올릴 때는 다음을 한 변경 세트에서 수행합니다.
|
|
106
|
+
|
|
107
|
+
1. 공식 Overview의 version과 category counts 확인
|
|
108
|
+
2. source name 중복과 총합 테스트 갱신
|
|
109
|
+
3. 새 source entry를 canonical HJM target에 연결
|
|
110
|
+
4. 제거·deprecated·new lifecycle 기록
|
|
111
|
+
5. `pnpm check`와 `pnpm showcase:web:build`로 public export와 정적 문서 검증
|
|
112
|
+
|
|
113
|
+
`pnpm reference:antd:verify`는 npm registry의 현재 `latest`와 고정 version을 비교합니다.
|
|
114
|
+
이 검사는 외부 네트워크와 upstream release 시점에 따라 결과가 달라지므로 일반 `push`/
|
|
115
|
+
`pull_request` 또는 예약 CI 게이트에 넣지 않습니다. 기준 버전을 검토할 때만 로컬에서 명시적으로
|
|
116
|
+
실행합니다. drift를 찾더라도 pin을 자동으로 바꾸지 않고 실패해, 위 다섯 항목을 한 변경 세트로
|
|
117
|
+
검토하게 합니다.
|
|
118
|
+
|
|
119
|
+
외부 시스템의 외형, token 값, prop 이름, 전용 아이콘·자산은 복사하지 않습니다.
|