@hjmds/design-contracts 1.27.1 → 1.28.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/activity-chip.d.ts +1 -1
- package/dist/activity-chip.js +1 -1
- package/dist/activity-chip.js.map +1 -1
- package/dist/auth-screen.d.ts +71 -5
- package/dist/auth-screen.d.ts.map +1 -1
- package/dist/auth-screen.js +60 -2
- package/dist/auth-screen.js.map +1 -1
- package/dist/base-recipes.d.ts +9 -1
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +7 -1
- package/dist/base-recipes.js.map +1 -1
- package/dist/behaviors.d.ts +1 -1
- package/dist/breadcrumb.d.ts +1 -1
- package/dist/breadcrumb.js +1 -1
- package/dist/breadcrumb.js.map +1 -1
- package/dist/calendar.d.ts +1 -1
- package/dist/calendar.js +2 -2
- package/dist/calendar.js.map +1 -1
- package/dist/catalog.d.ts +148 -89
- package/dist/catalog.d.ts.map +1 -1
- package/dist/component-contracts.d.ts +1 -1
- package/dist/component-contracts.js +1 -1
- package/dist/component-contracts.js.map +1 -1
- package/dist/component-recipes.d.ts +105 -60
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +138 -69
- package/dist/component-recipes.js.map +1 -1
- package/dist/conversation-row.d.ts +2 -2
- package/dist/conversation-row.d.ts.map +1 -1
- package/dist/conversation-row.js +6 -4
- package/dist/conversation-row.js.map +1 -1
- package/dist/counter-badge-recipe.d.ts +1 -1
- package/dist/counter-badge-recipe.js +1 -1
- package/dist/counter-badge-recipe.js.map +1 -1
- package/dist/data-table.d.ts +4 -4
- package/dist/data-table.js +4 -4
- package/dist/data-table.js.map +1 -1
- package/dist/date-picker.d.ts +1 -1
- package/dist/design-profile.d.ts.map +1 -1
- package/dist/design-profile.js +19 -16
- package/dist/design-profile.js.map +1 -1
- package/dist/file-picker.d.ts +1 -1
- package/dist/file-picker.js +1 -1
- package/dist/file-picker.js.map +1 -1
- package/dist/foundations.d.ts +18 -14
- package/dist/foundations.d.ts.map +1 -1
- package/dist/foundations.js +18 -13
- package/dist/foundations.js.map +1 -1
- package/dist/heading.d.ts +13 -13
- package/dist/heading.js.map +1 -1
- package/dist/icon-button-recipe.d.ts +23 -0
- package/dist/icon-button-recipe.d.ts.map +1 -1
- package/dist/icon-button-recipe.js +20 -1
- package/dist/icon-button-recipe.js.map +1 -1
- package/dist/number-field.d.ts +1 -1
- package/dist/otp-field.d.ts +1 -1
- package/dist/password-field.d.ts +1 -1
- package/dist/result.d.ts +1 -1
- package/dist/result.js +2 -2
- package/dist/result.js.map +1 -1
- package/dist/screen-patterns.d.ts +12 -1
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +12 -1
- package/dist/screen-patterns.js.map +1 -1
- package/dist/semantic-colors.d.ts +10 -0
- package/dist/semantic-colors.d.ts.map +1 -1
- package/dist/semantic-colors.js +6 -0
- package/dist/semantic-colors.js.map +1 -1
- package/dist/sheet.d.ts +7 -4
- package/dist/sheet.d.ts.map +1 -1
- package/dist/sheet.js +8 -5
- package/dist/sheet.js.map +1 -1
- package/dist/side-panel.d.ts +1 -1
- package/dist/side-panel.js +2 -2
- package/dist/side-panel.js.map +1 -1
- package/dist/steps.d.ts +1 -1
- package/dist/steps.js +1 -1
- package/dist/steps.js.map +1 -1
- package/dist/tag.d.ts +1 -1
- package/dist/tag.js +1 -1
- package/dist/tag.js.map +1 -1
- package/dist/timeline.d.ts +1 -1
- package/dist/timeline.js +1 -1
- package/dist/timeline.js.map +1 -1
- package/dist/top.d.ts +4 -4
- package/dist/top.d.ts.map +1 -1
- package/dist/top.js +3 -1
- package/dist/top.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/activity-chip.md +5 -1
- package/docs/auth-screen.md +2 -0
- package/docs/design-profile.md +14 -10
- package/docs/generated/component-maturity.md +1 -1
- package/docs/generated/renderer-evidence.json +9 -5
- package/docs/generated/renderer-evidence.md +1 -1
- package/docs/generated/showcase-manifest.json +1 -1
- package/docs/heading.md +14 -9
- package/docs/sheet.md +2 -0
- package/docs/top.md +4 -3
- package/docs/usage/README.md +1 -1
- package/docs/usage/components/auth-screen-layout.md +26 -9
- package/docs/usage/components/bottom-cta.md +6 -2
- package/docs/usage/components/bottom-navigation.md +5 -1
- package/docs/usage/components/breadcrumb.md +5 -1
- package/docs/usage/components/button.md +2 -0
- package/docs/usage/components/chat-message.md +6 -2
- package/docs/usage/components/checkbox-group.md +5 -1
- package/docs/usage/components/collapsible.md +1 -0
- package/docs/usage/components/comment-thread-row.md +1 -1
- package/docs/usage/components/effect-surface.md +2 -2
- package/docs/usage/components/heading.md +14 -12
- package/docs/usage/components/icon-button.md +5 -0
- package/docs/usage/components/inline-edit.md +1 -1
- package/docs/usage/components/list-detail-screen.md +1 -1
- package/docs/usage/components/list-row.md +23 -11
- package/docs/usage/components/list.md +10 -5
- package/docs/usage/components/notice.md +5 -1
- package/docs/usage/components/notification-item.md +5 -1
- package/docs/usage/components/photo-source-sheet.md +1 -1
- package/docs/usage/components/repeatable-field-group.md +1 -1
- package/docs/usage/components/saved-items-screen.md +1 -1
- package/docs/usage/components/screen-layout.md +10 -6
- package/docs/usage/components/search-screen.md +2 -2
- package/docs/usage/components/section.md +5 -1
- package/docs/usage/components/segmented-control.md +1 -1
- package/docs/usage/components/sheet.md +17 -13
- package/docs/usage/components/skeleton.md +41 -15
- package/docs/usage/components/statistic.md +5 -1
- package/docs/usage/components/switch.md +3 -2
- package/docs/usage/components/table-column-settings.md +1 -1
- package/docs/usage/components/tag.md +5 -1
- package/docs/usage/components/text.md +6 -4
- package/docs/usage/components/timeline.md +5 -1
- package/docs/usage/components/toast.md +10 -1
- package/docs/usage/components/top-bar.md +5 -1
- package/docs/usage/components/top.md +1 -1
- package/docs/usage/components/upload-item.md +1 -1
- package/docs/usage/compositions/adaptive-content.md +5 -1
- package/docs/usage/compositions/compound-controls.md +2 -1
- package/docs/usage/compositions/design-profile-comparison.md +1 -1
- package/docs/usage/compositions/family-drawer.md +5 -3
- package/docs/usage/compositions/input-sheet.md +1 -1
- package/docs/usage/compositions/interaction-flow-apply.md +3 -3
- package/docs/usage/compositions/photo-source.md +3 -3
- package/docs/usage/compositions/selection-motion.md +5 -1
- package/docs/usage/compositions/toast-layout.md +4 -4
- package/docs/usage/compositions/visual-foundations.md +5 -4
- package/docs/usage/screens/common-login.md +9 -7
- package/docs/usage/screens/common-search.md +1 -1
- package/docs/usage/screens/landing.md +15 -14
- package/docs/usage/screens/notification-settings.md +4 -2
- package/docs/usage/tokens/typography.md +21 -13
- package/package.json +1 -1
package/dist/timeline.d.ts
CHANGED
package/dist/timeline.js
CHANGED
package/dist/timeline.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { FontWeightValue, TextVariant } from \"./foundations.js\";\nimport { fontWeight, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * Timeline is a record of what already happened, not a position in a flow —\n * that is Steps' job (see docs/steps.md and the boundary note in\n * docs/timeline.md). Because there is no cursor, Timeline never derives a\n * status from position: every item is simply \"happened\", and the connector\n * between items carries no reached/unreached meaning. Ant Design's\n * alternating left/right layout is intentionally not reproduced — it is a\n * Web-only decoration with no equivalent in a single-direction native list;\n * see docs/timeline.md for the rationale.\n */\nexport type TimelineItemTone = \"neutral\" | \"info\" | \"success\" | \"attention\";\n\nexport type TimelineItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n /** Product-formatted \"when\" copy, e.g. \"3회 초\" or \"2024-01-15\". Timeline never formats a clock, inning, or date itself. */\n timestamp?: string;\n /** Product-formatted supplementary detail, e.g. \"안타로 1루 진출\". */\n description?: string;\n tone?: TimelineItemTone;\n}>;\n\nexport type TimelineDescriptor<Id extends string = string> = Readonly<{\n items: readonly TimelineItemDescriptor<Id>[];\n}>;\n\nexport const timelineDefaults = {\n itemTone: \"neutral\",\n} as const satisfies Readonly<{ itemTone: TimelineItemTone }>;\n\nexport type TimelineAccessibleNameInfo = Readonly<{\n position: number;\n total: number;\n label: string;\n}>;\n\n/**\n * Products own the exact phrase order and counter-word grammar, same reason\n * Steps does not assemble its own accessible name (see docs/steps.md).\n */\nexport type ComposeTimelineAccessibleName = (\n info: TimelineAccessibleNameInfo,\n) => string;\n\nexport type ResolvedTimelineItemDescriptor<Id extends string = string> = Omit<\n TimelineItemDescriptor<Id>,\n \"tone\"\n> &\n Readonly<{\n tone: TimelineItemTone;\n /** 1-indexed so position and label both preserve reading order for renderers. */\n position: number;\n total: number;\n /** Order + label, for platforms without built-in list-position semantics. */\n accessibleName: string;\n }>;\n\nexport type ResolveTimelineOptions = Readonly<{\n composeAccessibleName: ComposeTimelineAccessibleName;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Timeline ${field} must not be empty`);\n }\n}\n\nexport function validateTimelineItemDescriptor<Id extends string>(\n item: TimelineItemDescriptor<Id>,\n): void {\n assertNonEmpty(item.id, \"item id\");\n if (item.id !== item.id.trim()) {\n throw new TypeError(\"Timeline item id must not start or end with whitespace\");\n }\n assertNonEmpty(item.label, \"item label\");\n if (item.timestamp !== undefined) {\n assertNonEmpty(item.timestamp, \"item timestamp\");\n }\n if (item.description !== undefined) {\n assertNonEmpty(item.description, \"item description\");\n }\n if (\n item.tone !== undefined &&\n item.tone !== \"neutral\" &&\n item.tone !== \"info\" &&\n item.tone !== \"success\" &&\n item.tone !== \"attention\"\n ) {\n throw new TypeError(`Unsupported Timeline item tone: ${String(item.tone)}`);\n }\n}\n\nexport function validateTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n): void {\n if (descriptor.items.length === 0) {\n throw new RangeError(\"Timeline must contain at least one item\");\n }\n const ids = new Set<Id>();\n for (const item of descriptor.items) {\n validateTimelineItemDescriptor(item);\n if (ids.has(item.id)) {\n throw new TypeError(`Duplicate Timeline item id: ${item.id}`);\n }\n ids.add(item.id);\n }\n}\n\n/**\n * Attaches order-preserving accessible names to an already-ordered item list.\n * Unlike Steps, no status is derived from position — every item already\n * happened, so this only adds order and the resolved tone default.\n */\nexport function resolveTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n options: ResolveTimelineOptions,\n): readonly ResolvedTimelineItemDescriptor<Id>[] {\n validateTimelineDescriptor(descriptor);\n if (typeof options.composeAccessibleName !== \"function\") {\n throw new TypeError(\"Timeline composeAccessibleName must be a function\");\n }\n\n const total = descriptor.items.length;\n return descriptor.items.map((item, index) => {\n const position = index + 1;\n const accessibleName = options.composeAccessibleName({\n position,\n total,\n label: item.label,\n });\n if (typeof accessibleName !== \"string\" || accessibleName.trim().length === 0) {\n throw new TypeError(\n \"Timeline composeAccessibleName must return a non-empty string\",\n );\n }\n return {\n ...item,\n tone: item.tone ?? timelineDefaults.itemTone,\n position,\n total,\n accessibleName,\n };\n });\n}\n\nexport const timelineRecipe = {\n slots: [\n \"root\",\n \"item\",\n \"dot\",\n \"connector\",\n \"content\",\n \"timestamp\",\n \"label\",\n \"description\",\n ] as const,\n gap: spacing.md,\n dot: {\n diameter: 10,\n borderWidth: stroke.default,\n tones: {\n neutral: { border: null, fill: semanticColors.content.secondary },\n info: {\n border: semanticColors.feedback.info.border,\n fill: semanticColors.feedback.info.foreground,\n },\n success: {\n border: semanticColors.feedback.success.border,\n fill: semanticColors.feedback.success.foreground,\n },\n attention: {\n border: semanticColors.feedback.attention.border,\n fill: semanticColors.feedback.attention.foreground,\n },\n },\n },\n /**\n * A single tone, unlike stepsRecipe.connector's reached/unreached pair —\n * Timeline has no cursor, so no segment is \"not yet reached\".\n */\n connector: {\n width: stroke.default,\n tone: semanticColors.border.default,\n },\n timestamp: {\n textVariant: \"caption\",\n color: semanticColors.content.secondary,\n },\n label: {\n textVariant: \"body\",\n fontWeight: fontWeight.semibold,\n color: semanticColors.content.primary,\n },\n description: {\n textVariant: \"body\",\n color: semanticColors.content.body,\n },\n} as const satisfies {\n slots: readonly string[];\n gap: number;\n dot: {\n diameter: number;\n borderWidth: number;\n tones: Record<\n TimelineItemTone,\n { border: ColorReference | null; fill: ColorReference }\n >;\n };\n connector: {\n width: number;\n tone: ColorReference;\n };\n timestamp: { textVariant: TextVariant; color: ColorReference };\n label: { textVariant: TextVariant; fontWeight: FontWeightValue; color: ColorReference };\n description: { textVariant: TextVariant; color: ColorReference };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { FontWeightValue, TextVariant } from \"./foundations.js\";\nimport { fontWeight, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * Timeline is a record of what already happened, not a position in a flow —\n * that is Steps' job (see docs/steps.md and the boundary note in\n * docs/timeline.md). Because there is no cursor, Timeline never derives a\n * status from position: every item is simply \"happened\", and the connector\n * between items carries no reached/unreached meaning. Ant Design's\n * alternating left/right layout is intentionally not reproduced — it is a\n * Web-only decoration with no equivalent in a single-direction native list;\n * see docs/timeline.md for the rationale.\n */\nexport type TimelineItemTone = \"neutral\" | \"info\" | \"success\" | \"attention\";\n\nexport type TimelineItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n /** Product-formatted \"when\" copy, e.g. \"3회 초\" or \"2024-01-15\". Timeline never formats a clock, inning, or date itself. */\n timestamp?: string;\n /** Product-formatted supplementary detail, e.g. \"안타로 1루 진출\". */\n description?: string;\n tone?: TimelineItemTone;\n}>;\n\nexport type TimelineDescriptor<Id extends string = string> = Readonly<{\n items: readonly TimelineItemDescriptor<Id>[];\n}>;\n\nexport const timelineDefaults = {\n itemTone: \"neutral\",\n} as const satisfies Readonly<{ itemTone: TimelineItemTone }>;\n\nexport type TimelineAccessibleNameInfo = Readonly<{\n position: number;\n total: number;\n label: string;\n}>;\n\n/**\n * Products own the exact phrase order and counter-word grammar, same reason\n * Steps does not assemble its own accessible name (see docs/steps.md).\n */\nexport type ComposeTimelineAccessibleName = (\n info: TimelineAccessibleNameInfo,\n) => string;\n\nexport type ResolvedTimelineItemDescriptor<Id extends string = string> = Omit<\n TimelineItemDescriptor<Id>,\n \"tone\"\n> &\n Readonly<{\n tone: TimelineItemTone;\n /** 1-indexed so position and label both preserve reading order for renderers. */\n position: number;\n total: number;\n /** Order + label, for platforms without built-in list-position semantics. */\n accessibleName: string;\n }>;\n\nexport type ResolveTimelineOptions = Readonly<{\n composeAccessibleName: ComposeTimelineAccessibleName;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Timeline ${field} must not be empty`);\n }\n}\n\nexport function validateTimelineItemDescriptor<Id extends string>(\n item: TimelineItemDescriptor<Id>,\n): void {\n assertNonEmpty(item.id, \"item id\");\n if (item.id !== item.id.trim()) {\n throw new TypeError(\"Timeline item id must not start or end with whitespace\");\n }\n assertNonEmpty(item.label, \"item label\");\n if (item.timestamp !== undefined) {\n assertNonEmpty(item.timestamp, \"item timestamp\");\n }\n if (item.description !== undefined) {\n assertNonEmpty(item.description, \"item description\");\n }\n if (\n item.tone !== undefined &&\n item.tone !== \"neutral\" &&\n item.tone !== \"info\" &&\n item.tone !== \"success\" &&\n item.tone !== \"attention\"\n ) {\n throw new TypeError(`Unsupported Timeline item tone: ${String(item.tone)}`);\n }\n}\n\nexport function validateTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n): void {\n if (descriptor.items.length === 0) {\n throw new RangeError(\"Timeline must contain at least one item\");\n }\n const ids = new Set<Id>();\n for (const item of descriptor.items) {\n validateTimelineItemDescriptor(item);\n if (ids.has(item.id)) {\n throw new TypeError(`Duplicate Timeline item id: ${item.id}`);\n }\n ids.add(item.id);\n }\n}\n\n/**\n * Attaches order-preserving accessible names to an already-ordered item list.\n * Unlike Steps, no status is derived from position — every item already\n * happened, so this only adds order and the resolved tone default.\n */\nexport function resolveTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n options: ResolveTimelineOptions,\n): readonly ResolvedTimelineItemDescriptor<Id>[] {\n validateTimelineDescriptor(descriptor);\n if (typeof options.composeAccessibleName !== \"function\") {\n throw new TypeError(\"Timeline composeAccessibleName must be a function\");\n }\n\n const total = descriptor.items.length;\n return descriptor.items.map((item, index) => {\n const position = index + 1;\n const accessibleName = options.composeAccessibleName({\n position,\n total,\n label: item.label,\n });\n if (typeof accessibleName !== \"string\" || accessibleName.trim().length === 0) {\n throw new TypeError(\n \"Timeline composeAccessibleName must return a non-empty string\",\n );\n }\n return {\n ...item,\n tone: item.tone ?? timelineDefaults.itemTone,\n position,\n total,\n accessibleName,\n };\n });\n}\n\nexport const timelineRecipe = {\n slots: [\n \"root\",\n \"item\",\n \"dot\",\n \"connector\",\n \"content\",\n \"timestamp\",\n \"label\",\n \"description\",\n ] as const,\n gap: spacing.md,\n dot: {\n diameter: 10,\n borderWidth: stroke.default,\n tones: {\n neutral: { border: null, fill: semanticColors.content.secondary },\n info: {\n border: semanticColors.feedback.info.border,\n fill: semanticColors.feedback.info.foreground,\n },\n success: {\n border: semanticColors.feedback.success.border,\n fill: semanticColors.feedback.success.foreground,\n },\n attention: {\n border: semanticColors.feedback.attention.border,\n fill: semanticColors.feedback.attention.foreground,\n },\n },\n },\n /**\n * A single tone, unlike stepsRecipe.connector's reached/unreached pair —\n * Timeline has no cursor, so no segment is \"not yet reached\".\n */\n connector: {\n width: stroke.default,\n tone: semanticColors.border.default,\n },\n timestamp: {\n textVariant: \"caption\",\n color: semanticColors.content.secondary,\n },\n label: {\n textVariant: \"body\",\n fontWeight: fontWeight.medium,\n color: semanticColors.content.primary,\n },\n description: {\n textVariant: \"body\",\n color: semanticColors.content.body,\n },\n} as const satisfies {\n slots: readonly string[];\n gap: number;\n dot: {\n diameter: number;\n borderWidth: number;\n tones: Record<\n TimelineItemTone,\n { border: ColorReference | null; fill: ColorReference }\n >;\n };\n connector: {\n width: number;\n tone: ColorReference;\n };\n timestamp: { textVariant: TextVariant; color: ColorReference };\n label: { textVariant: TextVariant; fontWeight: FontWeightValue; color: ColorReference };\n description: { textVariant: TextVariant; color: ColorReference };\n};\n"]}
|
package/dist/top.d.ts
CHANGED
|
@@ -47,16 +47,16 @@ export declare const topRecipe: {
|
|
|
47
47
|
readonly title: {
|
|
48
48
|
readonly fontSize: 20;
|
|
49
49
|
readonly lineHeight: 28;
|
|
50
|
-
readonly fontWeight: "
|
|
50
|
+
readonly fontWeight: "500";
|
|
51
51
|
};
|
|
52
52
|
readonly paddingTop: 16;
|
|
53
53
|
readonly paddingBottom: 12;
|
|
54
54
|
};
|
|
55
55
|
readonly large: {
|
|
56
56
|
readonly title: {
|
|
57
|
-
readonly fontSize:
|
|
58
|
-
readonly lineHeight:
|
|
59
|
-
readonly fontWeight: "
|
|
57
|
+
readonly fontSize: 24;
|
|
58
|
+
readonly lineHeight: 32;
|
|
59
|
+
readonly fontWeight: "500";
|
|
60
60
|
};
|
|
61
61
|
readonly paddingTop: 24;
|
|
62
62
|
readonly paddingBottom: 16;
|
package/dist/top.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"top.d.ts","sourceRoot":"","sources":["../src/top.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEzC,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAC1B,CAAC,CAAC;AAEH,eAAO,MAAM,WAAW;;;CAGiD,CAAC;AAQ1E,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,aAAa,GAAG,IAAI,CAkBrE;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"top.d.ts","sourceRoot":"","sources":["../src/top.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEzC,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAC1B,CAAC,CAAC;AAEH,eAAO,MAAM,WAAW;;;CAGiD,CAAC;AAQ1E,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,aAAa,GAAG,IAAI,CAkBrE;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoBpB,uDAAuD;;;;;CAexD,CAAC;AAEF,eAAO,MAAM,WAAW;IACtB,6CAA6C;;;;;;;;;;;;;;;CAaV,CAAC"}
|
package/dist/top.js
CHANGED
|
@@ -37,7 +37,9 @@ export const topRecipe = {
|
|
|
37
37
|
defaults: { size: topDefaults.size },
|
|
38
38
|
sizes: {
|
|
39
39
|
medium: { title: typography.titleLarge, paddingTop: spacing.md, paddingBottom: spacing.sm },
|
|
40
|
-
|
|
40
|
+
// The screen's first title uses the HJM ceiling, `heading.level3` (24). It was `heading.level2` (32) until the
|
|
41
|
+
// display tier was removed (2026-10-17 user decision, "글자 너무 커").
|
|
42
|
+
large: { title: heading.level3, paddingTop: spacing.xl, paddingBottom: spacing.md },
|
|
41
43
|
},
|
|
42
44
|
eyebrow: {
|
|
43
45
|
color: semanticColors.content.brand,
|
package/dist/top.js.map
CHANGED
|
@@ -1 +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.
|
|
1
|
+
{"version":3,"file":"top.js","sourceRoot":"","sources":["../src/top.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAiCtD,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,OAAO;IACb,YAAY,EAAE,CAAC;CACwD,CAAC;AAE1E,SAAS,cAAc,CAAC,KAAa,EAAE,KAAa;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,SAAS,CAAC,OAAO,KAAK,oBAAoB,CAAC,CAAC;IACxD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,UAAyB;IAC7D,IAAI,UAAU,KAAK,IAAI,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC1D,MAAM,IAAI,SAAS,CAAC,kCAAkC,CAAC,CAAC;IAC1D,CAAC;IACD,cAAc,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC1C,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS;QAAE,cAAc,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACpF,IAAI,UAAU,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACzC,cAAc,CAAC,UAAU,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACjG,MAAM,IAAI,SAAS,CAAC,yBAAyB,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,IACE,UAAU,CAAC,YAAY,KAAK,SAAS;QACrC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,YAAY,CAAC,EAC5C,CAAC;QACD,MAAM,IAAI,SAAS,CAAC,iCAAiC,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAC1F,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,KAAK,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,CAAU;IACvE,QAAQ,EAAE,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,EAAE;IACpC,KAAK,EAAE;QACL,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,EAAE;QAC3F,+GAA+G;QAC/G,kEAAkE;QAClE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,EAAE;KACpF;IACD,OAAO,EAAE;QACP,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK;QACnC,WAAW,EAAE,OAAgB;QAC7B,YAAY,EAAE,OAAO,CAAC,GAAG;KAC1B;IACD,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO,EAAE;IAChD,WAAW,EAAE;QACX,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI;QAClC,WAAW,EAAE,MAAe;QAC5B,SAAS,EAAE,OAAO,CAAC,EAAE;KACtB;IACD,uDAAuD;IACvD,QAAQ,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE;IAC7B,GAAG,EAAE,OAAO,CAAC,EAAE;CAahB,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,6CAA6C;IAC7C,UAAU,EAAE,EAAE;IACd,MAAM,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,CAAC;IACnE,SAAS,EAAE,EAAE;IACb,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;IACxD,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IACtD,SAAS,EAAE;QACT,2EAA2E;QAC3E,oEAAoE;QACpE,gFAAgF;QAChF,+EAA+E;QAC/E,0EAA0E;KAC3E;CACkC,CAAC","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 // The screen's first title uses the HJM ceiling, `heading.level3` (24). It was `heading.level2` (32) until the\n // display tier was removed (2026-10-17 user decision, \"글자 너무 커\").\n large: { title: heading.level3, 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.level3; 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,QAAiB,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,QAAiB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.28.0\" as const;\n"]}
|
package/docs/activity-chip.md
CHANGED
|
@@ -19,7 +19,7 @@ Chip의 안정성을 상속하거나 독립적인 stable 승격을 뜻하지 않
|
|
|
19
19
|
- `emphasis`: `default`(surface/text/border) · `emphasis`(brand action fill/content, 결과가 사용자를 기다릴 때).
|
|
20
20
|
두 쌍 모두 light/dark에서 4.5:1을 contract test가 지킨다.
|
|
21
21
|
- `activityChipRecipe`: 최소 높이 44(`control.minTouchTarget`), 최대 폭 180, 모서리 full, 미디어 28, 간격 `spacing.xxs`,
|
|
22
|
-
시작 inset `spacing.xxs`(미디어가 있을 때)·끝 inset `spacing.sm`, 라벨 `label` variant
|
|
22
|
+
시작 inset `spacing.xxs`(미디어가 있을 때)·끝 inset `spacing.sm`, 라벨 `label` variant medium(500) 한 줄.
|
|
23
23
|
- 작업 상태·문구·미디어·패널 내용은 제품 소유다. HJM은 트리거 모양, 펼침 의미, 패널(Toast) 수명주기를 소유한다.
|
|
24
24
|
|
|
25
25
|
## Renderer
|
|
@@ -33,3 +33,7 @@ Chip의 안정성을 상속하거나 독립적인 stable 승격을 뜻하지 않
|
|
|
33
33
|
|
|
34
34
|
검증: contracts `test/activity-chip.test.ts`, Web `test/activity-chip.browser.test.tsx`, Native `test/activity-chip.test.tsx`.
|
|
35
35
|
기기 시각·VoiceOver/TalkBack 확인은 아직 하지 않았다.
|
|
36
|
+
|
|
37
|
+
## 근거
|
|
38
|
+
|
|
39
|
+
2026-10-17 사용자 결정("굵기 좀더 줄여"): 모든 글자 굵기를 한 단계 낮췄다. 큰 숫자·선택 상태 600(큰 제목 단계는 같은 날 폐지), 제목·강조 500, label 500, 본문 400이며 정본은 [타이포그래피](usage/tokens/typography.md)다.
|
package/docs/auth-screen.md
CHANGED
|
@@ -75,6 +75,8 @@ hero와 footer는 진행 중에도 유지한다. 버튼별 busy spinner와 함
|
|
|
75
75
|
| footer 간격 | `xl`(compact `md`) | **최소값**이다. 화면이 길면 그 이상 벌어진다 |
|
|
76
76
|
| 제공자 버튼 높이 | `authProviderButtonRecipe.minHeight` | 여기서 다시 정하지 않는다. 두 계약이 각자 값을 들면 한쪽만 바뀌어 버튼 줄기가 어긋난다 |
|
|
77
77
|
| 정책 링크 터치 | `control.minTouchTarget` | 글자 높이만 누를 수 있으면 44pt에 못 미친다 |
|
|
78
|
+
| 히어로 태그라인 | `typography.heading` 24/32·500(compact `titleLarge` 20/28) | 2026-10-17 "굵기랑 크기 줄여": 화면 첫 제목(display 32/40)을 빌리면 402 폭에서 두 줄이 된다. `AuthScreenHero`가 그린다 |
|
|
79
|
+
| 정책 링크 간격 | `linkGap` 8 + `·` + 8 | 2026-10-17 "간격 줄여": 버튼 안쪽 여백이 더해지면 40~42가 된다. 터치 영역은 보이지 않게 넓힌다. `AuthScreenFooter`가 그린다 |
|
|
78
80
|
|
|
79
81
|
## 이 계약에 없는 것
|
|
80
82
|
|
package/docs/design-profile.md
CHANGED
|
@@ -51,8 +51,8 @@ export const productDesign = defineHjmDesignProfile({
|
|
|
51
51
|
|
|
52
52
|
| 프리셋 | 팔레트 | 서체·제목 | 깊이·모서리 | 구성 |
|
|
53
53
|
| --- | --- | --- | --- | --- |
|
|
54
|
-
| minimal | 흰 바탕·무채색 잉크, hairline `border` #e6e6e6, 짙은 회색 행동(#3d3d3d) | ui
|
|
55
|
-
| editorial | 따뜻한 종이(#fdfbf7)·잉크(#1b1714), 와인 강조(#7a1f3d), 규칙선용 짙은 `border` #cfc5b8 | display·reading 시스템 serif stack
|
|
54
|
+
| minimal | 흰 바탕·무채색 잉크, hairline `border` #e6e6e6, 짙은 회색 행동(#3d3d3d) | ui 유지(제목 굵기는 공통 500), body 행간 26 | raised·floating 그림자 0(오버레이는 테두리와 scrim), 4/8/12/16 | cards(테두리·안쪽 24)/inline·dashboard |
|
|
55
|
+
| editorial | 따뜻한 종이(#fdfbf7)·잉크(#1b1714), 와인 강조(#7a1f3d), 규칙선용 짙은 `border` #cfc5b8 | display·reading 시스템 serif stack(제목 크기는 공통 단계), body 행간 26 | raised 그림자 0, 0/2/2/4 | rows(규칙선)/collapsible·editorial |
|
|
56
56
|
| terminal | 밝은 모드 녹회색(#c8d4c4)·짙은 녹색 잉크·호박갈색 강조, 어두운 모드 검정(#050a06)·녹색 형광 글자·호박색 강조 | ui=code이므로 display·reading도 monospace | 그림자 0, 모서리 0 | rows(규칙선)/collapsible·dashboard |
|
|
57
57
|
|
|
58
58
|
serif stack은 `ui-serif, Iowan Old Style, Georgia, AppleMyungjo, Batang, Noto Serif KR, serif`이며 폰트를 내려받지 않는다.
|
|
@@ -101,24 +101,28 @@ SegmentedControl·Tabs, ScreenLayout 및 optional OverviewScreen에 연결한다
|
|
|
101
101
|
유리의 실제 backdrop blur와 클레이 inset shadow는 아래 Surface 질감 계약으로 연결한다. Native의 선택형 host·OS·architecture 조건과 실제 기기 검증은 별도다.
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
##
|
|
104
|
+
## 제목 단계 지정하기
|
|
105
105
|
|
|
106
106
|
`tokens.heading`은 `level1`~`level5`의 fontSize/lineHeight/fontWeight를 부분 지정한다.
|
|
107
107
|
현재 Heading은 두 renderer 모두 이 값으로 그리고 문서 `semanticLevel`과 textScale을
|
|
108
|
-
따로 유지한다.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
따로 유지한다. 모든 프리셋이 foundation의 24/24/24/20/18px를 쓴다. 브루탈리즘은 굵기만
|
|
109
|
+
800으로 명시한다(두꺼운 테두리·딱딱한 그림자를 버티는 제목 무게, 2026-10-17 사용자 Storybook 비교).
|
|
110
|
+
|
|
111
|
+
**글자 크기 상한(2026-10-17 사용자 결정, "글자 너무 커", "큰 글자 정책은 없애").** 프로필의
|
|
112
|
+
`typography`와 `heading` 어느 단계도 `typography.heading`의 24px를 넘을 수 없고, 넘으면 `RangeError`다.
|
|
113
|
+
그 전에는 에디토리얼 56/42/30px, 브루탈리즘 48/38/30px 같은 확대 제목이 허용됐지만 Native Storybook
|
|
114
|
+
서비스 소개 화면에서 지나치게 커 보여 큰 글자 단계와 함께 없앴다. 프리셋 정체성은 팔레트·서체 역할·
|
|
115
|
+
그림자·모서리·구성 축으로 구분한다.
|
|
112
116
|
|
|
113
117
|
```ts
|
|
114
118
|
const productDesign = defineHjmDesignProfile({
|
|
115
119
|
extends: "paper",
|
|
116
|
-
tokens: { heading: {
|
|
120
|
+
tokens: { heading: { level3: { fontWeight: "600" } } },
|
|
117
121
|
});
|
|
118
122
|
```
|
|
119
123
|
|
|
120
|
-
미지정 값은 preset을 상속한다. `tokens.typography.heading
|
|
121
|
-
|
|
124
|
+
미지정 값은 preset을 상속한다. `tokens.typography.heading`은 Heading level1~3,
|
|
125
|
+
`titleLarge`/`title`은 level4/5의 변경 기본값이며, 같은 필드에 명시한 `tokens.heading`이
|
|
122
126
|
우선한다. 나머지 프로필 축·본문 Text typography와 문서 순서는 바꾸지 않는다.
|
|
123
127
|
지원하지 않는 persisted heading 단계는 조용히 무시하지 않고 거부한다.
|
|
124
128
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Component maturity and evidence manifest
|
|
2
2
|
|
|
3
|
-
> Generated from `@hjmds/design-contracts` 1.
|
|
3
|
+
> Generated from `@hjmds/design-contracts` 1.28.0. Do not edit this file directly; run `pnpm contracts:sync`.
|
|
4
4
|
|
|
5
5
|
This document is the human-readable projection of the same catalog and Showcase manifest consumed by product CI.
|
|
6
6
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"designSystemVersion": "1.
|
|
3
|
+
"designSystemVersion": "1.28.0",
|
|
4
4
|
"source": "packages/design-contracts/docs/generated/showcase-manifest.json",
|
|
5
5
|
"surfaces": {
|
|
6
6
|
"web": {
|
|
7
7
|
"packageName": "@hjmds/react",
|
|
8
|
-
"packageVersion": "1.
|
|
8
|
+
"packageVersion": "1.28.0",
|
|
9
9
|
"activeComponents": 105,
|
|
10
10
|
"claimedComponents": 103,
|
|
11
11
|
"completeScenarioComponents": 103,
|
|
@@ -1044,7 +1044,9 @@
|
|
|
1044
1044
|
"claimed": true,
|
|
1045
1045
|
"subpath": "./auth-screen",
|
|
1046
1046
|
"exportNames": [
|
|
1047
|
-
"AuthScreenLayout"
|
|
1047
|
+
"AuthScreenLayout",
|
|
1048
|
+
"AuthScreenHero",
|
|
1049
|
+
"AuthScreenFooter"
|
|
1048
1050
|
],
|
|
1049
1051
|
"proofs": [
|
|
1050
1052
|
{
|
|
@@ -5700,7 +5702,7 @@
|
|
|
5700
5702
|
},
|
|
5701
5703
|
"native": {
|
|
5702
5704
|
"packageName": "@hjmds/react-native",
|
|
5703
|
-
"packageVersion": "1.
|
|
5705
|
+
"packageVersion": "1.28.0",
|
|
5704
5706
|
"activeComponents": 87,
|
|
5705
5707
|
"claimedComponents": 84,
|
|
5706
5708
|
"completeScenarioComponents": 83,
|
|
@@ -6621,7 +6623,9 @@
|
|
|
6621
6623
|
"claimed": true,
|
|
6622
6624
|
"subpath": "./auth-screen",
|
|
6623
6625
|
"exportNames": [
|
|
6624
|
-
"AuthScreenLayout"
|
|
6626
|
+
"AuthScreenLayout",
|
|
6627
|
+
"AuthScreenHero",
|
|
6628
|
+
"AuthScreenFooter"
|
|
6625
6629
|
],
|
|
6626
6630
|
"proofs": [
|
|
6627
6631
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Renderer evidence coverage
|
|
2
2
|
|
|
3
|
-
> Generated for HJM 1.
|
|
3
|
+
> Generated for HJM 1.28.0. Do not edit directly; run `pnpm evidence:sync`.
|
|
4
4
|
|
|
5
5
|
This projection joins the canonical surface maturity manifest with first-party renderer claims. Missing scenarios are explicit beta promotion debt; stable surfaces are blocked by CI until none remain.
|
|
6
6
|
|
package/docs/heading.md
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# Heading contract
|
|
2
2
|
|
|
3
|
-
**문제.** 랜딩
|
|
3
|
+
**문제.** 랜딩 대표 제목, 결과 화면 제목, 카드 제목처럼 **문서 제목 단계를 실제로
|
|
4
4
|
그려야 하는 자리**.
|
|
5
5
|
|
|
6
|
-
**이미 있던 것을 꺼냈을 뿐이다.** `foundations`의 `heading
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
**이미 있던 것을 꺼냈을 뿐이다.** `foundations`의 `heading` 단계를 renderer로 노출하고
|
|
7
|
+
문서 단계(`h1`~`h6`)를 함께 낸다. 이 계약은 **새 크기를 만들지 않는다.** 크기는 `Text`와 같은
|
|
8
|
+
단계이며 가장 큰 값은 `typography.heading` 24px다.
|
|
9
|
+
|
|
10
|
+
**큰 글자 단계 폐지(2026-10-17 사용자 결정, "글자 너무 커", "큰 글자 정책은 없애").** 처음에는
|
|
11
|
+
level1 40px·level2 32px의 큰 글자 단계가 있었지만, Native Storybook 서비스 소개 화면의 히어로·구획 제목이
|
|
12
|
+
지나치게 커서 없앴다. `level1`·`level2`는 소비 코드 호환을 위해 이름만 남기고 `level3`과 같은 24/32/500이며
|
|
13
|
+
새 코드는 `level3`~`level5`로 위계를 만든다. 디자인 프로필도 24px를 넘는 제목 크기를 거부한다.
|
|
10
14
|
|
|
11
15
|
**Top·Section과 겹치지 않는다.**
|
|
12
16
|
|
|
@@ -25,16 +29,17 @@ level5 18px까지 다섯 단계가 있었지만 어떤 renderer도 노출하지
|
|
|
25
29
|
**Native.** 접근성 role은 `header` 하나뿐이라 문서 단계는 `aria-level`로 함께 싣는다.
|
|
26
30
|
|
|
27
31
|
|
|
28
|
-
## 디자인 프로필의
|
|
32
|
+
## 디자인 프로필의 제목 크기
|
|
29
33
|
|
|
30
34
|
2026-10-07 [Heading 갤러리](https://component.gallery/components/heading/)를 비교하며
|
|
31
35
|
현재 renderer가 level3~5만 프로필 typography에 연결하고 level1/2는 고정값으로 남긴
|
|
32
|
-
누락을 확인했다. 앱이 테마를 한 번 넣어
|
|
36
|
+
누락을 확인했다. 앱이 테마를 한 번 넣어 모든 제목 단계를 재사용하려는 요구 때문에
|
|
33
37
|
[디자인 프로필](design-profile.md)의 `tokens.heading`을 다섯 시각 단계로 둔다.
|
|
34
38
|
본문 크기에서 큰 제목을 임의로 배율 계산하는 대안은 계층과 행간이 달라질 수 있어
|
|
35
39
|
채택하지 않는다. 원본 갤러리의 수치·코드·폰트 자산을 복사한 것이 아니다.
|
|
36
40
|
|
|
37
|
-
프로필이 없으면
|
|
38
|
-
|
|
41
|
+
프로필이 없으면 24/24/24/20/18 크기를 쓴다(2026-10-17 큰 글자 단계 폐지 전에는 40/32/24/20/18).
|
|
42
|
+
level1~3은 typography heading, level4/5는 titleLarge/title alias를 helper에서 병합하고 명시적인 heading
|
|
43
|
+
override가 마지막에 우선한다. 결과가 24px를 넘으면 프로필 생성이 `RangeError`로 실패한다.
|
|
39
44
|
Web은 CSS 변수, Native는 Text host의 metrics로 반영하고 `semanticLevel`·문구·초점을
|
|
40
45
|
바꾸지 않는다. 테마 전환은 시각 크기만 바꾸며 실제 문서 단계는 기존 계약이 소유한다.
|
package/docs/sheet.md
CHANGED
|
@@ -84,3 +84,5 @@ Diairy QA W16 reproduced Chrome default backdrop blur undoing Dialog focus retur
|
|
|
84
84
|
0pt가 되고 검색 입력만 그려졌다(iPhone 17 Pro · iOS 26.5, 이전 코드로도 재현). 제품은 창 높이 `flexBasis`로 우회했다.
|
|
85
85
|
`auto`는 내용 높이 그대로다(늘 남는 공간이 없다). 바뀌는 모습: 고정 높이 시트의 `footer`가 본문 바로 아래가 아니라 시트 아래에 붙는다(Web과 같다).
|
|
86
86
|
버린 대안: 새 `bodyLayout` prop(기본값을 그대로 두면 Web과 다른 동작이 남고 제품마다 켜야 한다).
|
|
87
|
+
|
|
88
|
+
2026-10-17 사용자 결정 두 가지. 첫째, 시트는 위쪽에 보이는 제목 줄을 그리지 않는다(예: SearchScreen 필터 시트의 "필터"). 위 1.4·1.7의 제목 정렬·element 제목 설명은 기록으로 남긴다. 지금 `title`은 접근성 이름일 뿐이다. Web은 `aria-labelledby`가 가리키는 시각 숨김 `h2`, Native는 대화상자 `accessibilityLabel`과 배치 밖 1pt 머리글이다. Native element 제목은 그리지 않으므로 `accessibilityTitle`이 이름이 된다. 머리 행은 설명이나 ×가 있을 때만 그리고 ×는 끝 쪽 자리를 지킨다. Native Select·Combobox 선택 시트와 GestureSheet도 같은 규칙을 따르며 `sheetRecipe.title` 시각 값은 삭제했다. 둘째, 가림막을 누르면 모든 위치의 시트가 닫힌다(2026-10-16의 하단 드래그 전용 기본을 대체). 닫기 경로는 손잡이·드래그와 같은 `requestClose("outside")`이므로 `busy`·`dismissPolicy`·제어형 owner의 버리기 확인을 건너뛰지 않는다. 검사: `sheet-layout.browser.test.tsx`(접근성 이름, 머리 없음, 손잡이–본문 간격, 가림막 닫기·busy·owner 유지), `sheet-viewport.test.tsx`(Native 접근성 이름·보이는 제목 없음·× 위치·가림막 닫기·busy·`outsideDismiss: false`).
|
package/docs/top.md
CHANGED
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
|
|
19
19
|
**크기는 둘뿐이다.** `large`(화면의 첫 제목)와 `medium`(시트·모달 안의 첫 제목).
|
|
20
20
|
세 번째가 필요해지는 자리는 이미 `Section`이다 — 크기를 늘리는 대신 다른 컴포넌트를
|
|
21
|
-
쓰라는 신호로 둔다. 두 크기 모두 기존 타입 스케일(`heading.
|
|
22
|
-
그대로 쓰고 새 숫자를 만들지 않는다.
|
|
21
|
+
쓰라는 신호로 둔다. 두 크기 모두 기존 타입 스케일(`heading.level3` 24px, `typography.titleLarge` 20px)을
|
|
22
|
+
그대로 쓰고 새 숫자를 만들지 않는다. 2026-10-17 사용자 결정("글자 너무 커")으로 큰 글자 단계를 폐지하면서
|
|
23
|
+
`large`가 읽던 `heading.level2`(32px)를 HJM 상한인 `heading.level3`(24px)으로 바꿨다.
|
|
23
24
|
|
|
24
25
|
**잘리지 않는다.** `description`은 줄바꿈하며 말줄임하지 않는다. 큰 글자 설정에서 화면이
|
|
25
26
|
묻는 내용이 사라지면 안 되기 때문이다. `trailing` 보조 행동은 제목 줄을 공유하다가
|
|
@@ -33,6 +34,6 @@
|
|
|
33
34
|
|
|
34
35
|
## 프로필 모서리·제목 소비
|
|
35
36
|
|
|
36
|
-
2026-10-07 소비 감사에서 두 renderer의 제목 크기·행간·굵기가 foundation으로 확정된 recipe를 직접 읽어 프로필을 우회했다. `large`는 가까운 프로필의 `heading.
|
|
37
|
+
2026-10-07 소비 감사에서 두 renderer의 제목 크기·행간·굵기가 foundation으로 확정된 recipe를 직접 읽어 프로필을 우회했다. `large`는 가까운 프로필의 `heading.level3`, `medium`은 `typography.titleLarge`를 읽고 프로필이 없으면 기존 recipe를 유지한다. 시각 역할은 `headingLevel`과 독립이며 display 서체·제목 의미·보조 행동·초점은 바꾸지 않는다.
|
|
37
38
|
|
|
38
39
|
프로필 연결은 1.15.0 이후 미게시 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
|
package/docs/usage/README.md
CHANGED
|
@@ -97,7 +97,7 @@ Storybook 배포/실험은 예제 분류이며 npm 게시·기기 검증·제품
|
|
|
97
97
|
| [FollowProfileCard](components/follow-profile-card.md) | 구성/정보 표시 | 프로필 카드에서 팔로우·팔로잉 전환과 팔로워 수를 실제 mutation 결과와 함께 보여 줄 때 쓴다. | 1.21.0 | Web · Native | 개별 API 계약 참조 | 배포 |
|
|
98
98
|
| [Form](components/form.md) | 입력 | 여러 Field를 한 화면에 쌓고 한 번에 제출할 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
|
|
99
99
|
| [Grid](components/grid.md) | 레이아웃 | 카드·타일처럼 같은 모양의 자식을 창 크기에 따라 열 수를 바꿔 배치할 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
|
|
100
|
-
| [Heading](components/heading.md) | 글자와 아이콘 | 자리를 모르는
|
|
100
|
+
| [Heading](components/heading.md) | 글자와 아이콘 | 자리를 모르는 제목 글자 하나가 필요할 때 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
|
|
101
101
|
| [HistogramExplorer](components/histogram-explorer.md) | 구성/정보 표시 | 실제 관측값 배열의 분포를 동폭 구간 막대로 보여 주고, 사용자가 구간 수를 바꿔 볼 때 쓴다. | 1.21.0 | Web · Native | 개별 API 계약 참조 | 배포 |
|
|
102
102
|
| [Icon](components/icon.md) | 글자와 아이콘 | HJM semantic 이름(`search`, `back`, `chevronEnd`, `notifications` 등 43개)으로 고르는 그림 기호에 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
|
|
103
103
|
| [IconButton](components/icon-button.md) | 동작 | 보이는 글자 없이 아이콘만으로 표시하는 행동에 쓴다. | 1.12.1 | Web · Native | Web stable / Native stable | 배포 |
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
8
|
- 근거: [로그인 화면 골격](../../auth-screen.md), [1.4 제품 채택 가이드](../../product-adoption-1.4.md), recipe `authScreenRecipe`(`src/auth-screen.ts`), 포트폴리오 상위 기준 루트 `docs/LOGIN_SCREEN_STANDARD.md`(LS)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/레이아웃/로그인 화면`
|
|
10
10
|
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
로그인·가입 진입 화면의 배치에 쓴다. 위 영역(`hero` + `main`)은 남는 세로 공간에서 가운데 정렬되고,
|
|
14
14
|
`footer`(동의 고지·정책 링크)는 바닥에 붙는다. 내용이 화면보다 길면 스크롤로 바뀐다.
|
|
15
|
-
|
|
15
|
+
`AuthScreenLayout`은 배치를 소유하고, 히어로 문구와 동의 고지·정책 링크는 같은 모듈의 `AuthScreenHero`·`AuthScreenFooter`가
|
|
16
|
+
글자 단계·색·간격을 소유한다. 제품은 문자열·마크·링크 목적지만 넘긴다.
|
|
16
17
|
|
|
17
18
|
## 쓰지 않을 때
|
|
18
19
|
|
|
@@ -25,33 +26,35 @@
|
|
|
25
26
|
|
|
26
27
|
| 이름 | 역할 | Web | Native |
|
|
27
28
|
| --- | --- | --- | --- |
|
|
28
|
-
| `AuthScreenLayout` | 기본 | `@hjmds/react`, `/auth-screen` | `@hjmds/react-native`, `/auth-screen` |
|
|
29
|
+
| `AuthScreenLayout` | 기본(배치) | `@hjmds/react`, `/auth-screen` | `@hjmds/react-native`, `/auth-screen` |
|
|
30
|
+
| `AuthScreenHero` | `hero` 슬롯: 마크·eyebrow·태그라인·설명 | 같음 | 같음 |
|
|
31
|
+
| `AuthScreenFooter` | `footer` 슬롯: 동의 고지·정책 링크(·구분) | 같음, 링크 `href` 또는 `onClick` | 같음, 링크 `onPress` |
|
|
29
32
|
|
|
30
33
|
## 최소 사용 예
|
|
31
34
|
|
|
32
35
|
```tsx
|
|
33
36
|
// Web
|
|
34
|
-
import { AuthScreenLayout } from "@hjmds/react/auth-screen";
|
|
37
|
+
import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react/auth-screen";
|
|
35
38
|
|
|
36
39
|
<AuthScreenLayout
|
|
37
40
|
mainCard
|
|
38
41
|
{...(pending ? { pendingLabel: t("auth.pending") } : {})}
|
|
39
|
-
hero={<
|
|
42
|
+
hero={<AuthScreenHero mark={<ProductMark />} title={t("auth.tagline")} description={t("auth.description")} />}
|
|
40
43
|
main={<ProviderButtons />} // pending이어도 같은 목록
|
|
41
|
-
footer={<
|
|
44
|
+
footer={<AuthScreenFooter notice={t("auth.consent")} links={[{ label: t("legal.privacy"), href: privacyUrl }, { label: t("legal.terms"), href: termsUrl }]} />}
|
|
42
45
|
/>
|
|
43
46
|
```
|
|
44
47
|
|
|
45
48
|
```tsx
|
|
46
49
|
// Native
|
|
47
|
-
import { AuthScreenLayout } from "@hjmds/react-native/auth-screen";
|
|
50
|
+
import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react-native/auth-screen";
|
|
48
51
|
|
|
49
52
|
<AuthScreenLayout
|
|
50
53
|
mainCard
|
|
51
54
|
{...(pending ? { pendingLabel: t("auth.pending") } : {})}
|
|
52
|
-
hero={<
|
|
55
|
+
hero={<AuthScreenHero mark={<ProductMark />} title={t("auth.tagline")} description={t("auth.description")} />}
|
|
53
56
|
main={<ProviderButtons />}
|
|
54
|
-
footer={<
|
|
57
|
+
footer={<AuthScreenFooter links={[{ label: t("legal.privacy"), onPress: openPrivacy }, { label: t("legal.terms"), onPress: openTerms }]} />}
|
|
55
58
|
testID="login-screen"
|
|
56
59
|
/>
|
|
57
60
|
```
|
|
@@ -69,6 +72,8 @@ import { AuthScreenLayout } from "@hjmds/react-native/auth-screen";
|
|
|
69
72
|
| Web `as` | `main` · `section` | `main` | 최대 폭은 416이다 |
|
|
70
73
|
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 배치 전용(Web·Native 모두) |
|
|
71
74
|
| Native `testID` | `string` | — | 뿌리 `ScrollView` 식별 |
|
|
75
|
+
| `AuthScreenHero` `title` · `eyebrow` · `description` · `mark` · `headingLevel` | 문자열 · `ReactNode` · `1`~`3` | `headingLevel` 1 | 제목은 필수이고 빈 문자열이면 `TypeError`. 밀도는 바깥 `AuthScreenLayout`을 따른다 |
|
|
76
|
+
| `AuthScreenFooter` `links` · `notice` · `children` | 링크 배열(1개 이상) · `ReactNode` | — | 빈 링크 목록·빈 라벨은 `TypeError`(LS-04: 약관 링크는 로그인 전 같은 화면에 있어야 한다). `children`은 링크 아래 제품 내용 |
|
|
72
77
|
|
|
73
78
|
## 배치
|
|
74
79
|
|
|
@@ -76,6 +81,8 @@ import { AuthScreenLayout } from "@hjmds/react-native/auth-screen";
|
|
|
76
81
|
| --- | --- | --- |
|
|
77
82
|
| 크기 | 화면 전체(Web `min-block-size: 100dvh`, Native `ScrollView flex: 1`). 위 블록과 footer는 최대 폭 416, 마크 72(`markSize`, `radius.lg`), 정책 링크 최소 44(`footerMinTouchTarget`), mainCard 모서리 profile의 `radius.lg`(neutral/미지정 16) | `authScreenRecipe`, `.hjm-auth-screen*` |
|
|
78
83
|
| 간격 | `regular` / `compact`: 바깥 좌우 `layout.pagePadding.regular` 20 / `.compact` 16, 바깥 위아래 `spacing.xxxl` 40 / `spacing.lg` 20, hero 안 `spacing.md` 16 / `spacing.xs` 8, hero↔main `spacing.xl` 24 / `spacing.md` 16, 위 블록↔footer 최소 `spacing.xl` 24 / `spacing.md` 16. mainCard 안쪽 `spacing.md` 16 | `authScreenRecipe.densities`·`mainCard` |
|
|
84
|
+
| 히어로 문구 | 마크 ↔ 문구 묶음은 hero 간격. eyebrow `label` 12/18·500·브랜드색, 아래 `spacing.xxs` 4 → 태그라인 `regular` `typography.heading` 24/32 · `compact` `titleLarge` 20/28, 굵기는 제목 단계 500, 기본 글자색, 실제 heading 요소(Native `header`) → 설명 `body` 14/20·400·보조색(`muted`), 위 `spacing.xs` 8 | `authScreenRecipe.hero`, `densities.*.titleVariant` |
|
|
85
|
+
| 동의 고지·정책 링크 | 고지 `caption` 11/16 흐린 색, 링크 줄과 `spacing.xxs` 4. 링크 `caption`·브랜드색·밑줄, 링크 사이 `linkGap` 8 + `·` + 8(보이는 글자 간격 약 19). 각 링크 높이 44(배경 없음), 가로 터치 폭은 구분점 앞까지 보이지 않게 넓힘(Web 가상 요소, Native `hitSlop`) | `authScreenRecipe.footer`, `.hjm-auth-screen__link` |
|
|
79
86
|
| 순서·정렬 | 위→아래 hero(가운데 정렬 문구) → main(폭 꽉 채움) → footer(동의 고지·정책 링크). 위 블록은 남는 세로 공간에서 가운데, footer는 바닥에 붙는다. 진행 중 로딩은 main 카드 가운데에 겹친다 | `.hjm-auth-screen__block`(`justify-content: center`), `.hjm-auth-screen__pending` |
|
|
80
87
|
| 고정·스크롤 | 고정 영역이 없다. 넘치면 화면 전체가 스크롤한다. 안전 영역은 Native `contentInsetAdjustmentBehavior="automatic"`과 키보드 inset 자동 조정이 처리하고, Web은 safe-area inset을 더하지 않는다 | `react-native/src/auth-screen.tsx`, `.hjm-auth-screen` |
|
|
81
88
|
| 좁은 폭·큰 글자 | 키 작은 화면·큰 글자에서는 `compact`를 고른다. 그래도 넘치면 스크롤되며 footer는 맨 아래로 밀린다 | `authScreenRecipe.densities` |
|
|
@@ -104,6 +111,8 @@ import { AuthScreenLayout } from "@hjmds/react-native/auth-screen";
|
|
|
104
111
|
- `pendingLabel`은 스크린리더 안내로만 쓰인다. 로딩 아래에 보이는 문구를 따로 두지 않는다.
|
|
105
112
|
완료·취소·실패 시 prop을 제거한다. 취소는 실패 문장 없이 버튼만 복구한다(LS-09).
|
|
106
113
|
- `pendingLabel`은 기존 카드에서 전환하는 상태다. 제공자 목록 최초 조회의 로딩을 대신하지 않는다.
|
|
114
|
+
- 히어로에 `Top`(화면 첫 제목, large = display 32/40)이나 직접 조립한 `Text`를 넣지 않는다. `AuthScreenHero`를 쓴다.
|
|
115
|
+
정책 링크를 ghost `Button` + Stack gap으로 만들지 않는다 — 버튼 안쪽 여백이 간격에 더해진다. `AuthScreenFooter`를 쓴다.
|
|
107
116
|
- 슬롯 내용은 전부 제품 소유다: 마크 자산, 모든 문구(i18n 키), 제공자 목록과 순서, 정책 링크 목적지,
|
|
108
117
|
심사자 폼 노출 조건. 제공자 로고를 HJM에 넣지 않는다([AuthProviderButton](auth-provider-button.md)).
|
|
109
118
|
- iOS 앱의 제공자 목록에는 Apple이 있어야 한다(LS-02). 심사자 이메일 폼은 서버 env와 `review=1` 두 조건이
|
|
@@ -131,3 +140,11 @@ import { AuthScreenLayout } from "@hjmds/react-native/auth-screen";
|
|
|
131
140
|
### 테마 카드 모서리
|
|
132
141
|
|
|
133
142
|
`mainCard`는 가장 가까운 Provider의 `designProfile.tokens.radius.lg`를 양 플랫폼에서 소비한다. 테마가 없으면 기존 recipe의 16을 유지한다. 제공자 버튼은 제공자 브랜드 규격을 유지하며, 처리 중에도 카드와 입력을 제거하지 않는다. 카드 표면과 제공자 브랜드를 같은 radius로 강제하면 제품 테마가 제공자 규격을 덮기 때문에 역할을 나눈다.
|
|
143
|
+
|
|
144
|
+
## 근거
|
|
145
|
+
|
|
146
|
+
- `AuthScreenHero`·`AuthScreenFooter`와 히어로·링크 수치: 2026-10-17 사용자 피드백("굵기랑 크기 줄여", "간격 줄여").
|
|
147
|
+
예제가 히어로에 `Top large`(display 32/40·600)를 넣어 402 폭에서 태그라인이 두 줄이 됐고, 정책 링크는 ghost Button
|
|
148
|
+
안쪽 여백 12+12에 Stack gap 16이 더해져 글자 사이가 Web 42·Native 40이었다. 슬롯 내용을 앱이 조립하면 같은 실수가
|
|
149
|
+
제품마다 반복되므로 HJM이 글자 단계·간격을 recipe로 소유한다. 변경 후 Web 402에서 태그라인 24/32·500 한 줄,
|
|
150
|
+
링크 글자 간격 약 19, 링크 높이 44를 측정했다. 루트 LS §3도 같은 값으로 갱신했다.
|
|
@@ -79,7 +79,7 @@ const insets = useSafeAreaInsets();
|
|
|
79
79
|
| 간격 | 좌우 `layout.pagePadding.regular` 20, 위 `spacing.sm` 12, 아래 `spacing.sm` 12와 하단 inset 중 큰 값(Web은 `env(safe-area-inset-bottom)`·`safeAreaBottom`도 비교). 설명↔행동, 행동 사이 `spacing.sm` 12. 위 경계는 `border.default` 1px(`stroke.default`), Native는 위로 드리우는 그림자(opacity 0.08, radius 8) | `bottomCtaRecipe`, `.hjm-bottom-cta` |
|
|
80
80
|
| 순서·정렬 | 위→아래 [설명 한 줄(caption, muted)] → 행동 줄. 행동 줄은 [보조][주] 순서로 같은 폭을 나눈다. 주 행동은 하나다 | `react/src/bottom-cta.tsx`, `react-native/src/actions.tsx` |
|
|
81
81
|
| 고정·스크롤 | 본문 스크롤 영역 아래에 붙는다. Web은 `flow`(문서 흐름 끝) 또는 `sticky`(아래 0에 붙음, z-index `layer.sticky` 100). Native는 제품 화면 레이아웃이 스크롤 영역 밖 아래에 둔다. 키보드가 열리는 폼은 `KeyboardAvoiding` 또는 `KeyboardDock`으로 감싼다 | `.hjm-bottom-cta[data-position="sticky"]`, `react-native/src/keyboard-controller.tsx` |
|
|
82
|
-
| `ScreenLayout` `footer` 안 | footer가 위 경계·`screenPatternRecipe.padding` 16·바탕·safe area를 맡으므로 BottomCTA는 바(경계·여백·바탕·그림자·최소 높이)를 그리지 않고 설명과 행동 줄만 그린다. 직접 넘긴 `safeAreaBottom
|
|
82
|
+
| `ScreenLayout` `footer` 안 | footer가 위 경계·`screenPatternRecipe.padding` 16·바탕·safe area를 맡으므로 BottomCTA는 바(경계·여백·바탕·그림자·최소 높이)를 그리지 않고 설명과 행동 줄만 그린다. 버튼 아래 여백은 `max(footer 아래 여백, safeAreaBottom)`이다 — 직접 넘긴 `safeAreaBottom`은 footer가 이미 준 여백을 넘는 만큼만 더한다(16 + 34가 아니라 34). Native는 화면 inset을 ScreenLayout `safeAreaBottom`으로 넘기는 것이 기본이다. 2026-10-17 화면 리뷰: footer 안의 BottomCTA가 경계를 두 겹 긋고 버튼을 한 번 더 들였으며 Native는 footer 안에 그림자 있는 흰 상자를 하나 더 그렸다 | 내부 `ScreenFooterContext`, Web `.hjm-bottom-cta[data-placement="screen-footer"]`, Native `BottomCTA` |
|
|
83
83
|
| 좁은 폭·큰 글자 | Web은 행동 칸 기준 폭 132(`control.minTouchTarget` × 3)보다 좁으면 줄바꿈한다. 큰 글자에서는 세로로 쌓고 주 행동이 위에 온다 | `.hjm-bottom-cta__actions > div`, `.hjm-bottom-cta[data-large-text="true"]` |
|
|
84
84
|
|
|
85
85
|
```text
|
|
@@ -103,7 +103,7 @@ const insets = useSafeAreaInsets();
|
|
|
103
103
|
Native Form은 내장 제출 버튼(`submitLabel` 필수)을 항상 그리므로 Form 대신 필드 + contracts `createFormSubmitSession`
|
|
104
104
|
(`@hjmds/design-contracts/components/form`)으로 제출 세션을 잡고 그 상태를 `primaryAction.loading`에 연결한다.
|
|
105
105
|
- 배치는 `layoutStyle`로만 한다. Native `style`은 제거된 속성이다.
|
|
106
|
-
- 하단 inset을 넘긴다. Native는 자동으로 읽지 않으므로 `safeAreaBottom`이 없으면 홈 인디케이터에 붙는다.
|
|
106
|
+
- 하단 inset을 넘긴다. Native는 자동으로 읽지 않으므로 `safeAreaBottom`이 없으면 홈 인디케이터에 붙는다. ScreenLayout `footer` 안이면 ScreenLayout `safeAreaBottom`에 넘기고, host SafeAreaView의 아래 edge와 같이 쓰지 않는다(inset 위에 footer 16이 더해진다).
|
|
107
107
|
- 키보드가 열리는 폼 화면(Native)은 `KeyboardAvoiding`(`/keyboard`) 또는 `KeyboardDock`(`/keyboard-controller`,
|
|
108
108
|
optional peer `react-native-keyboard-controller` 설치 필요)로 감싼다.
|
|
109
109
|
|
|
@@ -126,3 +126,7 @@ const insets = useSafeAreaInsets();
|
|
|
126
126
|
- Native는 하단 inset을 스스로 읽지 않는다. `safeAreaBottom`을 빠뜨려도 오류가 없고 홈 인디케이터에 붙어 보인다.
|
|
127
127
|
|
|
128
128
|
1.15.0 변경: 프로필의 `shadow.floating` 색·강도·반경을 상속하되, 위 콘텐츠와 겹치는 footer 역할이라 offsetY는 `-abs(offsetY)`로 위쪽에 표시한다. Native의 0-opacity 프로필은 Android elevation도 0이다. 프로필 없는 Web은 기존 무그림자, Native는 기존 위쪽 recipe 그림자를 유지한다. safe area·큰 글자·로딩/실패와 제품 행동 소유권은 바뀌지 않는다.
|
|
129
|
+
|
|
130
|
+
## 근거
|
|
131
|
+
|
|
132
|
+
- footer 안 하단 inset은 합이 아니라 max: 2026-10-17 사용자 보고 "전체적으로 버튼 아래 여백이 많아". footer 16 위에 `safeAreaBottom`을 더해 홈 인디케이터 기기에서 버튼 아래가 50이 됐다. 마지막 컨트롤 아래 여백을 footer 위 여백 16과 같게 두고, 홈 인디케이터가 있으면 max(16, inset)만 둔다([ScreenLayout 근거](screen-layout.md#근거)).
|
|
@@ -98,7 +98,7 @@ Native floating 프레임·선택 표시·항목 모서리는 각 recipe radius
|
|
|
98
98
|
| --- | --- | --- |
|
|
99
99
|
| 크기 | 항목 최소 `regular` 56×64 · `compact` 52×52. 표면 최대 폭 `bar` 제한 없음(화면 폭) · `floating` 384 · `capsule` 480. 모서리 `floating` `radius.xl` 24 · `capsule` `radius.full`. 아이콘·배지 자리 40×28 | `bottomNavigationRecipe.density`·`presentations`·`indicator` |
|
|
100
100
|
| 간격 | 항목 안쪽 `regular` `spacing.xs` 8 · `compact` `spacing.xxs` 4, 아이콘↔라벨 `regular` `spacing.xxs` 4 · `compact` 2. `floating`·`capsule`은 바깥 좌우 `spacing.md` 16 · 위 `spacing.xs` 8. 하단은 안전 영역 + `spacing.xs` 8(Native는 `safeAreaBottom`을 최소 padding에 더함). `center-gap` 가운데 빈칸 `control.buttonHeight.large` + `spacing.md` = 68 | `presentations`, `distributions`, `safeArea`, `.hjm-bottom-navigation` |
|
|
101
|
-
| 순서·정렬 | 항목은 논리 순서(RTL이면 뒤집힘)로 같은 폭을 나눈다. 라벨은 아이콘 아래 가운데. `primaryAction`은 막대 정가운데에 겹친다(`center-gap` 빈칸 또는 `capsule`). 배지는 아이콘 끝·위 모서리(`blockStart` −4, `inlineEnd` −8) | `.hjm-bottom-navigation__list`·`__primary-action`, `bottomNavigationRecipe.badge`·`direction` |
|
|
101
|
+
| 순서·정렬 | 항목은 논리 순서(RTL이면 뒤집힘)로 같은 폭을 나눈다. 예외로 `capsule`에서 라벨을 펼치지 않는 상태(항목 4개 이하·글자 배율 1.5 미만)는 선택 항목이 2칸 폭 알약에 아이콘+라벨을 가로로 두고 나머지 항목은 아이콘만 남긴다(Web·Native 같은 규칙). 그래서 첫·끝 아이콘의 막대 끝 거리는 다르지만 항목 상자의 양끝 여백은 같다. `selection: "tile"` 선택 카드의 아이콘·라벨 줄은 다른 항목과 같다. 라벨은 아이콘 아래 가운데. `primaryAction`은 막대 정가운데에 겹친다(`center-gap` 빈칸 또는 `capsule`). 배지는 아이콘 끝·위 모서리(`blockStart` −4, `inlineEnd` −8) | `.hjm-bottom-navigation__list`·`__primary-action`, `bottomNavigationRecipe.badge`·`direction` |
|
|
102
102
|
| 고정·스크롤 | Web은 화면 아래 `position: fixed`(z-index `layer.sticky` 100). 본문 마지막 내용이 막대 아래로 들어가므로 제품 레이아웃이 하단 여백을 둔다. Native는 navigator 탭 막대 자리에 제품 레이아웃이 둔다. 키보드가 열리면 기본으로 숨는다 | `.hjm-bottom-navigation`, `bottomNavigationRecipe.adaptive`·`keyboard` |
|
|
103
103
|
| 좁은 폭·큰 글자 | 라벨은 줄바꿈되고 항목 높이가 늘어난다(고정 높이 없음). 글자 배율은 최대 1.4배까지만 커진다 | `bottomNavigationRecipe.largeText`·`label.wrap` |
|
|
104
104
|
|
|
@@ -165,3 +165,7 @@ floating 그림자는 그리지 않는다. 옆면이 높이를 표현하므로
|
|
|
165
165
|
팔레트에 "눌린 브랜드" 역할이 없어서 새 역할을 추가하지 않고 기존 역할을 겹쳐 쓴다.
|
|
166
166
|
선택하지 않은 카드도 같은 두께의 투명 테두리를 둔다. 그래서 선택이 바뀌어도 아이콘·라벨 줄이 움직이지 않는다.
|
|
167
167
|
제품은 나무 같은 색을 surfaceAlt·borderControl 팔레트 역할로 공급한다. deprecated 시각 style로 덮지 않는다.
|
|
168
|
+
|
|
169
|
+
## 근거
|
|
170
|
+
|
|
171
|
+
- capsule 선택 알약·tile 선택 카드: 2026-10-17 정렬 실측(Web 402·1280, 실험 내비게이션 바 비교). 첫·끝 아이콘의 막대 끝 거리 차이 17~23px은 선택 항목의 `flex: 2` 알약 때문이며 항목 상자 양끝 여백은 5/5로 같았다. 가운데 항목이 14.5px 솟아 보인 값은 tile 선택 카드 상자를 다른 항목의 라벨과 비교한 측정 오차였고 아이콘 244·라벨 268이 모든 항목에서 같았다. 두 renderer에 이미 같은 규칙이 있어 코드는 바꾸지 않고 배치 표에 의도를 적었다.
|
|
@@ -65,7 +65,7 @@ Native renderer는 없다.
|
|
|
65
65
|
| --- | --- | --- |
|
|
66
66
|
| 크기 | 내용 폭만 차지하는 한 줄 경로. 글자는 `label` 변형(12/18). 링크 높이가 44에 못 미치므로 터치 중심 화면의 주 탐색으로 쓰지 않는다 | `breadcrumbRecipe.link`·`current` |
|
|
67
67
|
| 간격 | 항목·구분자 사이 `spacing.xxs` 4. 바깥 여백이 없으므로 아래 제목·본문과의 간격은 화면의 세로 [Stack](stack.md)이 정한다 | `breadcrumbRecipe.gap`, `.hjm-breadcrumb` |
|
|
68
|
-
| 순서·정렬 | 페이지 제목 바로 위, 본문 시작 쪽 정렬. 상위 → 현재 순서이고 현재 위치(마지막)는 링크가 아니며
|
|
68
|
+
| 순서·정렬 | 페이지 제목 바로 위, 본문 시작 쪽 정렬. 상위 → 현재 순서이고 현재 위치(마지막)는 링크가 아니며 medium(500)으로 표시한다 | `.hjm-breadcrumb__current` |
|
|
69
69
|
| 고정·스크롤 | 고정되지 않고 본문과 함께 스크롤한다 | `.hjm-breadcrumb__list` |
|
|
70
70
|
| 좁은 폭·큰 글자 | 줄바꿈된다(`flex-wrap: wrap`). 항목 label도 줄바꿈되고 구분자는 줄어들지 않는다 | `.hjm-breadcrumb__list`, `.hjm-breadcrumb__separator { flex-shrink: 0 }` |
|
|
71
71
|
|
|
@@ -79,3 +79,7 @@ Native renderer는 없다.
|
|
|
79
79
|
|
|
80
80
|
- 조상 항목은 일반 `<a href>`로 그린다. 라우터 adapter prop(`renderLink` 등)이 없으므로
|
|
81
81
|
Next.js `Link` 같은 클라이언트 전환을 기대하지 않는다.
|
|
82
|
+
|
|
83
|
+
## 근거
|
|
84
|
+
|
|
85
|
+
2026-10-17 사용자 결정("굵기 좀더 줄여"): 모든 글자 굵기를 한 단계 낮췄다. 큰 숫자·선택 상태 600(큰 제목 단계는 같은 날 폐지), 제목·강조 500, label 500, 본문 400이며 정본은 [타이포그래피](../tokens/typography.md)다.
|