@hjmds/design-contracts 1.27.2 → 1.28.1
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 +77 -5
- package/dist/auth-screen.d.ts.map +1 -1
- package/dist/auth-screen.js +63 -2
- package/dist/auth-screen.js.map +1 -1
- package/dist/base-recipes.d.ts +1 -1
- package/dist/base-recipes.js +1 -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 +131 -87
- 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 +85 -57
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +98 -60
- 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/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 +36 -0
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +20 -0
- 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/SELECTION.md +1 -0
- package/docs/usage/components/auth-screen-layout.md +44 -11
- package/docs/usage/components/bottom-cta.md +6 -2
- package/docs/usage/components/breadcrumb.md +5 -1
- package/docs/usage/components/chat-screen.md +1 -0
- package/docs/usage/components/checkbox-group.md +5 -1
- package/docs/usage/components/collapsible.md +1 -0
- package/docs/usage/components/comment-thread-screen.md +2 -0
- package/docs/usage/components/dialog.md +1 -0
- package/docs/usage/components/editor-screen.md +1 -0
- package/docs/usage/components/effect-surface.md +2 -2
- package/docs/usage/components/field.md +1 -0
- package/docs/usage/components/heading.md +14 -12
- package/docs/usage/components/list-detail-screen.md +1 -0
- package/docs/usage/components/list-row.md +7 -3
- package/docs/usage/components/list.md +25 -4
- package/docs/usage/components/notification-item.md +5 -1
- package/docs/usage/components/onboarding-screen.md +1 -0
- package/docs/usage/components/photo-source-sheet.md +1 -1
- package/docs/usage/components/screen-layout.md +28 -3
- package/docs/usage/components/search-field.md +1 -0
- package/docs/usage/components/search-screen.md +4 -2
- package/docs/usage/components/section.md +5 -1
- package/docs/usage/components/segmented-control.md +1 -1
- package/docs/usage/components/sheet.md +18 -13
- package/docs/usage/components/skeleton.md +42 -15
- package/docs/usage/components/statistic.md +5 -1
- package/docs/usage/components/switch.md +3 -2
- package/docs/usage/components/tag.md +5 -1
- package/docs/usage/components/text-area.md +1 -0
- package/docs/usage/components/text.md +6 -4
- package/docs/usage/components/top.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/visual-foundations.md +5 -4
- package/docs/usage/screens/common-login.md +9 -7
- 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/activity-chip.d.ts
CHANGED
package/dist/activity-chip.js
CHANGED
|
@@ -46,7 +46,7 @@ export const activityChipRecipe = {
|
|
|
46
46
|
// 28 keeps a mascot legible inside the 44 row with 8pt of air; it is decorative (hidden from AT).
|
|
47
47
|
media: { size: 28, radius: "full" },
|
|
48
48
|
// One line: the chip is a glance status, the panel carries the long copy.
|
|
49
|
-
label: { textVariant: "label", fontWeight: fontWeight.
|
|
49
|
+
label: { textVariant: "label", fontWeight: fontWeight.medium, maxLines: 1 },
|
|
50
50
|
emphasis: {
|
|
51
51
|
// A resting status sits on the header like a quiet surface; the border keeps its edge on canvas.
|
|
52
52
|
default: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-chip.js","sourceRoot":"","sources":["../src/activity-chip.ts"],"names":[],"mappings":"AACA,OAAO,EACL,OAAO,EACP,UAAU,EACV,OAAO,EACP,MAAM,EACN,OAAO,EACP,MAAM,GAGP,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAiCtD,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,QAAQ,EAAE,SAAS;IACnB,QAAQ,EAAE,KAAK;CACmE,CAAC;AAErF,kFAAkF;AAClF,MAAM,UAAU,6BAA6B,CAC3C,UAAkC;IAElC,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QACrE,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAC;IAC9D,CAAC;IACD,IACE,UAAU,CAAC,kBAAkB,KAAK,SAAS;QAC3C,CAAC,OAAO,UAAU,CAAC,kBAAkB,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,EAC5F,CAAC;QACD,MAAM,IAAI,SAAS,CAAC,mDAAmD,CAAC,CAAC;IAC3E,CAAC;IACD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,IAAI,oBAAoB,CAAC,QAAQ,CAAC;IACtE,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;QACtD,MAAM,IAAI,SAAS,CAAC,sCAAsC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,UAAU,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QAClF,MAAM,IAAI,SAAS,CAAC,yCAAyC,CAAC,CAAC;IACjE,CAAC;IACD,OAAO;QACL,KAAK,EAAE,UAAU,CAAC,KAAK;QACvB,kBAAkB,EAAE,UAAU,CAAC,kBAAkB,IAAI,UAAU,CAAC,KAAK;QACrE,QAAQ;QACR,QAAQ,EAAE,UAAU,CAAC,QAAQ,IAAI,oBAAoB,CAAC,QAAQ;KAC/D,CAAC;AACJ,CAAC;AAsBD,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAU;IAC1C,QAAQ,EAAE,oBAAoB;IAC9B,8FAA8F;IAC9F,qFAAqF;IACrF,IAAI,EAAE;QACJ,SAAS,EAAE,OAAO,CAAC,cAAc;QACjC,QAAQ,EAAE,GAAG;QACb,MAAM,EAAE,MAAM;QACd,WAAW,EAAE,MAAM,CAAC,OAAO;QAC3B,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,+FAA+F;QAC/F,YAAY,EAAE,OAAO,CAAC,GAAG;QACzB,UAAU,EAAE,OAAO,CAAC,EAAE;QACtB,cAAc,EAAE,OAAO,CAAC,OAAO;KAChC;IACD,kGAAkG;IAClG,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;IACnC,0EAA0E;IAC1E,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"activity-chip.js","sourceRoot":"","sources":["../src/activity-chip.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport {\n control,\n fontWeight,\n opacity,\n radius,\n spacing,\n stroke,\n type FontWeightValue,\n type TextVariant,\n} from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * ActivityChip — a compact pressable status for one running background task (a review, an upload,\n * a sync). It shows a small decorative media and a short label in an otherwise empty header slot,\n * and opens the matching panel toast for details (composition `activity-status`).\n *\n * Why a new component (2026-10-10, BurnTok \"똑쟁이\" request): `Chip` is a selection/action control\n * with pressed/checked semantics; a status that toggles a details panel is a disclosure trigger with\n * `aria-expanded`, so reusing Chip would announce the wrong role. The name differs because Chip exists.\n * The task state, its copy and the panel content stay product-owned; HJM owns the trigger shape,\n * disclosure semantics and the panel (Toast) lifecycle.\n */\nexport type ActivityChipEmphasis = \"default\" | \"emphasis\";\n\nexport type ActivityChipDescriptor = Readonly<{\n /** Short visible status, for example \"다듬는 중\". */\n label: string;\n /** Full spoken status; defaults to `label`. */\n accessibilityLabel?: string;\n /** `emphasis` marks a result that waits for the user (e.g. a ready version to apply). */\n emphasis?: ActivityChipEmphasis;\n /** Whether the details panel it controls is open. */\n expanded?: boolean;\n}>;\n\nexport type ResolvedActivityChipDescriptor = Readonly<{\n label: string;\n accessibilityLabel: string;\n emphasis: ActivityChipEmphasis;\n expanded: boolean;\n}>;\n\nexport const activityChipDefaults = {\n emphasis: \"default\",\n expanded: false,\n} as const satisfies Readonly<{ emphasis: ActivityChipEmphasis; expanded: boolean }>;\n\n/** Validates product copy once so Web and Native reject the same empty labels. */\nexport function resolveActivityChipDescriptor(\n descriptor: ActivityChipDescriptor,\n): ResolvedActivityChipDescriptor {\n if (typeof descriptor.label !== \"string\" || !descriptor.label.trim()) {\n throw new TypeError(\"ActivityChip label must not be empty\");\n }\n if (\n descriptor.accessibilityLabel !== undefined &&\n (typeof descriptor.accessibilityLabel !== \"string\" || !descriptor.accessibilityLabel.trim())\n ) {\n throw new TypeError(\"ActivityChip accessibilityLabel must not be empty\");\n }\n const emphasis = descriptor.emphasis ?? activityChipDefaults.emphasis;\n if (emphasis !== \"default\" && emphasis !== \"emphasis\") {\n throw new TypeError(`Unsupported ActivityChip emphasis: ${String(emphasis)}`);\n }\n if (descriptor.expanded !== undefined && typeof descriptor.expanded !== \"boolean\") {\n throw new TypeError(\"ActivityChip expanded must be a boolean\");\n }\n return {\n label: descriptor.label,\n accessibilityLabel: descriptor.accessibilityLabel ?? descriptor.label,\n emphasis,\n expanded: descriptor.expanded ?? activityChipDefaults.expanded,\n };\n}\n\nexport type ActivityChipRecipe = Readonly<{\n slots: readonly [\"root\", \"media\", \"label\"];\n defaults: typeof activityChipDefaults;\n root: Readonly<{\n minHeight: number;\n maxWidth: number;\n radius: keyof typeof radius;\n borderWidth: number;\n gap: number;\n paddingStart: number;\n paddingEnd: number;\n pressedOpacity: number;\n }>;\n media: Readonly<{ size: number; radius: keyof typeof radius }>;\n label: Readonly<{ textVariant: TextVariant; fontWeight: FontWeightValue; maxLines: number }>;\n emphasis: Readonly<\n Record<ActivityChipEmphasis, Readonly<{ background: ColorReference; foreground: ColorReference; border: ColorReference }>>\n >;\n}>;\n\nexport const activityChipRecipe = {\n slots: [\"root\", \"media\", \"label\"] as const,\n defaults: activityChipDefaults,\n // The 44pt minimum keeps the whole chip one touch target; the width cap keeps a header's side\n // controls visible on a 320pt screen (BurnTok header: back 44 + three actions ~150).\n root: {\n minHeight: control.minTouchTarget,\n maxWidth: 180,\n radius: \"full\",\n borderWidth: stroke.default,\n gap: spacing.xxs,\n // The media hugs the start edge like an avatar chip; the label end keeps the wider pill inset.\n paddingStart: spacing.xxs,\n paddingEnd: spacing.sm,\n pressedOpacity: opacity.pressed,\n },\n // 28 keeps a mascot legible inside the 44 row with 8pt of air; it is decorative (hidden from AT).\n media: { size: 28, radius: \"full\" },\n // One line: the chip is a glance status, the panel carries the long copy.\n label: { textVariant: \"label\", fontWeight: fontWeight.medium, maxLines: 1 },\n emphasis: {\n // A resting status sits on the header like a quiet surface; the border keeps its edge on canvas.\n default: {\n background: semanticColors.surface.default,\n foreground: semanticColors.content.primary,\n border: semanticColors.border.default,\n },\n // The brand action pair is the contrast-checked fill/content couple (same as a primary Button).\n emphasis: {\n background: semanticColors.action.brand.background,\n foreground: semanticColors.action.brand.content,\n border: semanticColors.action.brand.background,\n },\n },\n} as const satisfies ActivityChipRecipe;\n"]}
|
package/dist/auth-screen.d.ts
CHANGED
|
@@ -41,6 +41,30 @@ export type ResolvedAuthScreenDescriptor = Readonly<{
|
|
|
41
41
|
paddingInline: number;
|
|
42
42
|
paddingBlock: number;
|
|
43
43
|
maxWidth: number;
|
|
44
|
+
/** 히어로 제목의 글자 단계. `AuthScreenHero`가 이 값으로 그린다. */
|
|
45
|
+
titleVariant: AuthScreenTitleVariant;
|
|
46
|
+
}>;
|
|
47
|
+
export type AuthScreenTitleVariant = "heading" | "titleLarge";
|
|
48
|
+
/**
|
|
49
|
+
* 히어로 문구. 문자열은 전부 제품 소유이고, 글자 단계·색·간격은 `authScreenRecipe.hero`가 소유한다.
|
|
50
|
+
* 마크 자산은 렌더러의 `mark` 슬롯으로 받는다.
|
|
51
|
+
*/
|
|
52
|
+
export type AuthScreenHeroDescriptor = Readonly<{
|
|
53
|
+
title: string;
|
|
54
|
+
eyebrow?: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
/** 문서 구조상의 제목 단계. 로그인 화면이 라우트의 첫 제목이므로 기본은 1이다. */
|
|
57
|
+
headingLevel?: 1 | 2 | 3;
|
|
58
|
+
}>;
|
|
59
|
+
/** 정책 링크 한 개. 목적지와 문구는 제품 소유다. */
|
|
60
|
+
export type AuthScreenPolicyLinkDescriptor = Readonly<{
|
|
61
|
+
label: string;
|
|
62
|
+
/**
|
|
63
|
+
* Supplementary announcement after the label, e.g. the product's localized "opens in browser" (Web aria-describedby
|
|
64
|
+
* text / RN accessibilityHint). The visible label stays the link's name; spint dropped its hint when it adopted
|
|
65
|
+
* AuthScreenFooter because the links had no slot for it (2026-10-17).
|
|
66
|
+
*/
|
|
67
|
+
accessibilityHint?: string;
|
|
44
68
|
}>;
|
|
45
69
|
export declare const authScreenDefaults: {
|
|
46
70
|
readonly density: "regular";
|
|
@@ -52,7 +76,7 @@ export declare const authScreenDefaults: {
|
|
|
52
76
|
* 읽기 폭을 쓸 이유가 없다.
|
|
53
77
|
*/
|
|
54
78
|
export declare const authScreenRecipe: {
|
|
55
|
-
readonly slots: readonly ["root", "hero", "mark", "title", "description", "main", "footer"];
|
|
79
|
+
readonly slots: readonly ["root", "hero", "mark", "eyebrow", "title", "description", "main", "footer", "notice", "links"];
|
|
56
80
|
readonly defaults: {
|
|
57
81
|
readonly density: "regular";
|
|
58
82
|
readonly hasFooter: true;
|
|
@@ -61,16 +85,38 @@ export declare const authScreenRecipe: {
|
|
|
61
85
|
/** 마크는 아이콘(48)보다 크고 일러스트(120+)보다 작다 — 제품을 알아볼 최소 크기다. */
|
|
62
86
|
readonly markSize: 72;
|
|
63
87
|
readonly markRadius: 16;
|
|
88
|
+
/**
|
|
89
|
+
* 히어로 제목은 LS의 태그라인(24)이다. 2026-10-17 사용자 피드백("굵기랑 크기 줄여"): 예제가
|
|
90
|
+
* 화면 첫 제목용 `Top large`(display 32/40)를 히어로에 넣어 402 폭에서 두 줄로 화면을 눌렀다.
|
|
91
|
+
* 로그인 슬로건은 화면 제목이 아니라 제공자 버튼을 소개하는 한 줄이라 display 단계를 쓰지
|
|
92
|
+
* 않고, 굵기는 제목 단계의 medium(500)을 그대로 따른다. compact는 한 단계 낮춘다(densities).
|
|
93
|
+
*/
|
|
64
94
|
readonly title: {
|
|
65
|
-
readonly fontSize:
|
|
66
|
-
readonly lineHeight:
|
|
67
|
-
readonly fontWeight: "
|
|
95
|
+
readonly fontSize: 24;
|
|
96
|
+
readonly lineHeight: 32;
|
|
97
|
+
readonly fontWeight: "500";
|
|
68
98
|
};
|
|
69
99
|
readonly description: {
|
|
70
100
|
readonly fontSize: 14;
|
|
71
101
|
readonly lineHeight: 20;
|
|
72
102
|
readonly fontWeight: "400";
|
|
73
103
|
};
|
|
104
|
+
readonly hero: {
|
|
105
|
+
readonly eyebrow: {
|
|
106
|
+
readonly textVariant: "label";
|
|
107
|
+
readonly tone: "brand";
|
|
108
|
+
readonly gapBelow: 4;
|
|
109
|
+
};
|
|
110
|
+
readonly title: {
|
|
111
|
+
readonly tone: "primary";
|
|
112
|
+
};
|
|
113
|
+
/** LS 설명은 보조 글자색이다 — 본문색이면 태그라인과 같은 무게로 읽힌다. */
|
|
114
|
+
readonly description: {
|
|
115
|
+
readonly textVariant: "body";
|
|
116
|
+
readonly tone: "muted";
|
|
117
|
+
readonly gapAbove: 8;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
74
120
|
/** 정책 링크는 글자 높이만큼만 누를 수 있으면 44pt 기준에 못 미친다. */
|
|
75
121
|
readonly footerMinTouchTarget: 44;
|
|
76
122
|
readonly footerText: {
|
|
@@ -78,6 +124,27 @@ export declare const authScreenRecipe: {
|
|
|
78
124
|
readonly lineHeight: 16;
|
|
79
125
|
readonly fontWeight: "400";
|
|
80
126
|
};
|
|
127
|
+
/**
|
|
128
|
+
* 동의 고지와 정책 링크(LS §3: 고지 `caption` 흐린 색, 링크와 gap `xxs`, 링크 `caption` 브랜드색·밑줄,
|
|
129
|
+
* "개인정보 처리방침 · 이용약관"). 2026-10-17 사용자 피드백("간격 줄여"): 예제가 ghost Button 두 개를
|
|
130
|
+
* Stack gap 16으로 놓아 버튼 안쪽 여백 12+12가 더해져 글자 사이가 40~42로 벌어졌다. 보이는 간격은
|
|
131
|
+
* `linkGap` + 구분점 + `linkGap` 하나로 정하고, 44 터치 높이는 보이지 않는 히트 영역(Web 높이·가상
|
|
132
|
+
* 요소, Native minHeight·hitSlop)으로 채운다 — 보이는 여백으로 터치 영역을 만들면 간격이 다시 커진다.
|
|
133
|
+
*/
|
|
134
|
+
readonly footer: {
|
|
135
|
+
readonly notice: {
|
|
136
|
+
readonly textVariant: "caption";
|
|
137
|
+
readonly tone: "muted";
|
|
138
|
+
};
|
|
139
|
+
readonly noticeGap: 4;
|
|
140
|
+
readonly link: {
|
|
141
|
+
readonly textVariant: "caption";
|
|
142
|
+
readonly tone: "brand";
|
|
143
|
+
readonly underline: true;
|
|
144
|
+
};
|
|
145
|
+
readonly linkGap: 8;
|
|
146
|
+
readonly separator: "·";
|
|
147
|
+
};
|
|
81
148
|
/** 제공자 버튼 높이는 Provider 계약이 소유한다 — 여기서 다시 정하지 않는다. */
|
|
82
149
|
readonly providerMinHeight: 44;
|
|
83
150
|
readonly mainCard: {
|
|
@@ -91,6 +158,7 @@ export declare const authScreenRecipe: {
|
|
|
91
158
|
readonly footerGap: 24;
|
|
92
159
|
readonly paddingInline: 20;
|
|
93
160
|
readonly paddingBlock: 40;
|
|
161
|
+
readonly titleVariant: "heading";
|
|
94
162
|
};
|
|
95
163
|
readonly compact: {
|
|
96
164
|
readonly heroGap: 8;
|
|
@@ -98,10 +166,14 @@ export declare const authScreenRecipe: {
|
|
|
98
166
|
readonly footerGap: 16;
|
|
99
167
|
readonly paddingInline: 16;
|
|
100
168
|
readonly paddingBlock: 20;
|
|
169
|
+
readonly titleVariant: "titleLarge";
|
|
101
170
|
};
|
|
102
171
|
};
|
|
103
172
|
};
|
|
104
173
|
export declare function validateAuthScreenDescriptor(descriptor: AuthScreenDescriptor): void;
|
|
174
|
+
export declare function validateAuthScreenHeroDescriptor(descriptor: AuthScreenHeroDescriptor): void;
|
|
175
|
+
/** LS-04: 약관·개인정보처리방침 링크는 로그인 전에 같은 화면에 있어야 하므로 빈 목록을 받지 않는다. */
|
|
176
|
+
export declare function validateAuthScreenPolicyLinks(links: readonly AuthScreenPolicyLinkDescriptor[]): void;
|
|
105
177
|
export declare function resolveAuthScreenDescriptor(descriptor?: AuthScreenDescriptor): ResolvedAuthScreenDescriptor;
|
|
106
178
|
export declare const authScreenBehavior: {
|
|
107
179
|
readonly controlled: readonly [];
|
|
@@ -118,6 +190,6 @@ export declare const authScreenBehavior: {
|
|
|
118
190
|
readonly states: readonly [];
|
|
119
191
|
readonly actions: readonly [];
|
|
120
192
|
};
|
|
121
|
-
readonly scenarios: readonly ["the-hero-and-the-main-action-stay-one-vertically-centred-block", "the-footer-sits-at-the-bottom-and-never-overlaps-the-main-action", "content-taller-than-the-viewport-scrolls-instead-of-pushing-the-footer-off-screen", "the-product-owns-every-string-and-the-mark-asset-while-the-layout-stays-here", "provider-button-height-comes-from-the-provider-contract-not-from-this-one", "compact-density-shrinks-gaps-without-changing-the-two-region-structure", "policy-links-in-the-footer-keep-the-minimum-touch-target"];
|
|
193
|
+
readonly scenarios: readonly ["the-hero-and-the-main-action-stay-one-vertically-centred-block", "the-footer-sits-at-the-bottom-and-never-overlaps-the-main-action", "content-taller-than-the-viewport-scrolls-instead-of-pushing-the-footer-off-screen", "the-product-owns-every-string-and-the-mark-asset-while-the-layout-stays-here", "provider-button-height-comes-from-the-provider-contract-not-from-this-one", "compact-density-shrinks-gaps-without-changing-the-two-region-structure", "policy-links-in-the-footer-keep-the-minimum-touch-target", "the-hero-title-uses-the-recipe-title-step-not-a-display-heading", "policy-links-are-separated-by-one-recipe-gap-and-a-dot-while-the-touch-area-stays-invisible"];
|
|
122
194
|
};
|
|
123
195
|
//# sourceMappingURL=auth-screen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-screen.d.ts","sourceRoot":"","sources":["../src/auth-screen.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtD,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;;OAGG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,OAAO,EAAE,iBAAiB,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-screen.d.ts","sourceRoot":"","sources":["../src/auth-screen.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtD,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;;OAGG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,OAAO,EAAE,iBAAiB,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,YAAY,EAAE,sBAAsB,CAAC;CACtC,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,YAAY,CAAC;AAE9D;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAC1B,CAAC,CAAC;AAEH,kCAAkC;AAClC,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CAAC;IACpD,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;CAGoB,CAAC;AAEpD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;IAI3B,yDAAyD;;;IAGzD;;;;;OAKG;;;;;;;;;;;;;;;;;;;;QAMD,gDAAgD;;;;;;;IAGlD,+CAA+C;;;;;;;IAG/C;;;;;;OAMG;;;;;;;;;;;;;;;IAQH,qDAAqD;;;;;;;;;;;;;;;;;;;;;;;;CAuB7C,CAAC;AAIX,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,oBAAoB,GAAG,IAAI,CAUnF;AAQD,wBAAgB,gCAAgC,CAAC,UAAU,EAAE,wBAAwB,GAAG,IAAI,CAU3F;AAED,iEAAiE;AACjE,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,SAAS,8BAA8B,EAAE,GAAG,IAAI,CAQpG;AAED,wBAAgB,2BAA2B,CACzC,UAAU,GAAE,oBAAyB,GACpC,4BAA4B,CAe9B;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;CAkBM,CAAC"}
|
package/dist/auth-screen.js
CHANGED
|
@@ -10,17 +10,43 @@ export const authScreenDefaults = {
|
|
|
10
10
|
* 읽기 폭을 쓸 이유가 없다.
|
|
11
11
|
*/
|
|
12
12
|
export const authScreenRecipe = {
|
|
13
|
-
slots: ["root", "hero", "mark", "title", "description", "main", "footer"],
|
|
13
|
+
slots: ["root", "hero", "mark", "eyebrow", "title", "description", "main", "footer", "notice", "links"],
|
|
14
14
|
defaults: authScreenDefaults,
|
|
15
15
|
maxWidth: 416,
|
|
16
16
|
/** 마크는 아이콘(48)보다 크고 일러스트(120+)보다 작다 — 제품을 알아볼 최소 크기다. */
|
|
17
17
|
markSize: 72,
|
|
18
18
|
markRadius: radius.lg,
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* 히어로 제목은 LS의 태그라인(24)이다. 2026-10-17 사용자 피드백("굵기랑 크기 줄여"): 예제가
|
|
21
|
+
* 화면 첫 제목용 `Top large`(display 32/40)를 히어로에 넣어 402 폭에서 두 줄로 화면을 눌렀다.
|
|
22
|
+
* 로그인 슬로건은 화면 제목이 아니라 제공자 버튼을 소개하는 한 줄이라 display 단계를 쓰지
|
|
23
|
+
* 않고, 굵기는 제목 단계의 medium(500)을 그대로 따른다. compact는 한 단계 낮춘다(densities).
|
|
24
|
+
*/
|
|
25
|
+
title: typography.heading,
|
|
20
26
|
description: typography.body,
|
|
27
|
+
hero: {
|
|
28
|
+
eyebrow: { textVariant: "label", tone: "brand", gapBelow: spacing.xxs },
|
|
29
|
+
title: { tone: "primary" },
|
|
30
|
+
/** LS 설명은 보조 글자색이다 — 본문색이면 태그라인과 같은 무게로 읽힌다. */
|
|
31
|
+
description: { textVariant: "body", tone: "muted", gapAbove: spacing.xs },
|
|
32
|
+
},
|
|
21
33
|
/** 정책 링크는 글자 높이만큼만 누를 수 있으면 44pt 기준에 못 미친다. */
|
|
22
34
|
footerMinTouchTarget: control.minTouchTarget,
|
|
23
35
|
footerText: typography.caption,
|
|
36
|
+
/**
|
|
37
|
+
* 동의 고지와 정책 링크(LS §3: 고지 `caption` 흐린 색, 링크와 gap `xxs`, 링크 `caption` 브랜드색·밑줄,
|
|
38
|
+
* "개인정보 처리방침 · 이용약관"). 2026-10-17 사용자 피드백("간격 줄여"): 예제가 ghost Button 두 개를
|
|
39
|
+
* Stack gap 16으로 놓아 버튼 안쪽 여백 12+12가 더해져 글자 사이가 40~42로 벌어졌다. 보이는 간격은
|
|
40
|
+
* `linkGap` + 구분점 + `linkGap` 하나로 정하고, 44 터치 높이는 보이지 않는 히트 영역(Web 높이·가상
|
|
41
|
+
* 요소, Native minHeight·hitSlop)으로 채운다 — 보이는 여백으로 터치 영역을 만들면 간격이 다시 커진다.
|
|
42
|
+
*/
|
|
43
|
+
footer: {
|
|
44
|
+
notice: { textVariant: "caption", tone: "muted" },
|
|
45
|
+
noticeGap: spacing.xxs,
|
|
46
|
+
link: { textVariant: "caption", tone: "brand", underline: true },
|
|
47
|
+
linkGap: spacing.xs,
|
|
48
|
+
separator: "·",
|
|
49
|
+
},
|
|
24
50
|
/** 제공자 버튼 높이는 Provider 계약이 소유한다 — 여기서 다시 정하지 않는다. */
|
|
25
51
|
providerMinHeight: authProviderButtonRecipe.minHeight,
|
|
26
52
|
// 2026-10-01: the action card stays mounted during login so its dimensions and background survive the loader.
|
|
@@ -32,6 +58,7 @@ export const authScreenRecipe = {
|
|
|
32
58
|
footerGap: spacing.xl,
|
|
33
59
|
paddingInline: layout.pagePadding.regular,
|
|
34
60
|
paddingBlock: spacing.xxxl,
|
|
61
|
+
titleVariant: "heading",
|
|
35
62
|
},
|
|
36
63
|
compact: {
|
|
37
64
|
heroGap: spacing.xs,
|
|
@@ -39,6 +66,8 @@ export const authScreenRecipe = {
|
|
|
39
66
|
footerGap: spacing.md,
|
|
40
67
|
paddingInline: layout.pagePadding.compact,
|
|
41
68
|
paddingBlock: spacing.lg,
|
|
69
|
+
// 세로가 좁은 화면에서는 제목도 한 단계 낮춰 제공자 버튼을 첫 화면에 남긴다.
|
|
70
|
+
titleVariant: "titleLarge",
|
|
42
71
|
},
|
|
43
72
|
},
|
|
44
73
|
};
|
|
@@ -54,6 +83,35 @@ export function validateAuthScreenDescriptor(descriptor) {
|
|
|
54
83
|
throw new TypeError("AuthScreen hasFooter must be a boolean");
|
|
55
84
|
}
|
|
56
85
|
}
|
|
86
|
+
function assertNonEmpty(value, field) {
|
|
87
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
88
|
+
throw new TypeError(`AuthScreen ${field} must not be empty`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function validateAuthScreenHeroDescriptor(descriptor) {
|
|
92
|
+
if (descriptor === null || typeof descriptor !== "object") {
|
|
93
|
+
throw new TypeError("AuthScreen hero must be an object");
|
|
94
|
+
}
|
|
95
|
+
assertNonEmpty(descriptor.title, "hero title");
|
|
96
|
+
if (descriptor.eyebrow !== undefined)
|
|
97
|
+
assertNonEmpty(descriptor.eyebrow, "hero eyebrow");
|
|
98
|
+
if (descriptor.description !== undefined)
|
|
99
|
+
assertNonEmpty(descriptor.description, "hero description");
|
|
100
|
+
if (descriptor.headingLevel !== undefined && ![1, 2, 3].includes(descriptor.headingLevel)) {
|
|
101
|
+
throw new TypeError(`Unsupported AuthScreen hero headingLevel: ${String(descriptor.headingLevel)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** LS-04: 약관·개인정보처리방침 링크는 로그인 전에 같은 화면에 있어야 하므로 빈 목록을 받지 않는다. */
|
|
105
|
+
export function validateAuthScreenPolicyLinks(links) {
|
|
106
|
+
if (!Array.isArray(links) || links.length === 0) {
|
|
107
|
+
throw new TypeError("AuthScreen footer needs at least one policy link");
|
|
108
|
+
}
|
|
109
|
+
for (const link of links) {
|
|
110
|
+
assertNonEmpty(link?.label, "policy link label");
|
|
111
|
+
if (link.accessibilityHint !== undefined)
|
|
112
|
+
assertNonEmpty(link.accessibilityHint, "policy link accessibilityHint");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
57
115
|
export function resolveAuthScreenDescriptor(descriptor = {}) {
|
|
58
116
|
validateAuthScreenDescriptor(descriptor);
|
|
59
117
|
const density = descriptor.density ?? authScreenDefaults.density;
|
|
@@ -67,6 +125,7 @@ export function resolveAuthScreenDescriptor(descriptor = {}) {
|
|
|
67
125
|
paddingInline: scale.paddingInline,
|
|
68
126
|
paddingBlock: scale.paddingBlock,
|
|
69
127
|
maxWidth: authScreenRecipe.maxWidth,
|
|
128
|
+
titleVariant: scale.titleVariant,
|
|
70
129
|
};
|
|
71
130
|
}
|
|
72
131
|
export const authScreenBehavior = {
|
|
@@ -84,6 +143,8 @@ export const authScreenBehavior = {
|
|
|
84
143
|
"provider-button-height-comes-from-the-provider-contract-not-from-this-one",
|
|
85
144
|
"compact-density-shrinks-gaps-without-changing-the-two-region-structure",
|
|
86
145
|
"policy-links-in-the-footer-keep-the-minimum-touch-target",
|
|
146
|
+
"the-hero-title-uses-the-recipe-title-step-not-a-display-heading",
|
|
147
|
+
"policy-links-are-separated-by-one-recipe-gap-and-a-dot-while-the-touch-area-stays-invisible",
|
|
87
148
|
],
|
|
88
149
|
};
|
|
89
150
|
//# sourceMappingURL=auth-screen.js.map
|
package/dist/auth-screen.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-screen.js","sourceRoot":"","sources":["../src/auth-screen.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport { authProviderButtonRecipe } from \"./provider-button.js\";\nimport { control, layout, radius, spacing, typography } from \"./foundations.js\";\n\n/**\n * 로그인 화면의 골격이다. 제품이 화면을 다시 조립하지 않게 **배치만** 소유한다.\n *\n * 왜 골격인가: 계정이 있는 제품들이 같은 화면을 각자 만들었고, 같은 실수를 각자 했다.\n * 번뚝은 로고·태그라인·설명 두 줄·동의 고지·정책 링크를 위에서부터 쌓아 제공자 버튼을\n * 화면 밖으로 밀어냈고(2026-09-19 iPhone 17 Pro 실측), 그 배치를 따라한 다른 제품도 같은\n * 순서로 쌓였다. 간격과 최대 폭은 제품마다 다를 이유가 없는데 제품마다 다시 정해졌다.\n *\n * 그래서 **두 영역**만 강제한다. 위 영역(`hero` + `main`)은 남는 세로 공간을 전부 차지하고\n * 그 안에서 가운데 정렬한다. 아래 영역(`footer`)은 바닥에 붙는다. 세 영역 이상으로 나누지\n * 않는 이유는, 셋이 되는 순간 어느 것이 주 행동인지가 배치로 드러나지 않아서다.\n *\n * **HJM이 소유하는 것**: 영역 구분, 간격, 최대 폭, 가운데 정렬, 내용이 길어질 때의 스크롤 전환.\n * **제품이 소유하는 것**: 마크 자산, 모든 문구, 제공자 목록과 순서, 정책 링크의 목적지,\n * 그리고 `main`에 무엇을 넣을지(제공자 버튼만, 또는 가입 재개·심사자 입력 같은 제품 덩어리).\n *\n * 이 계약은 화면을 **그리지 않는다.** `AuthProviderButton`·`BottomInfo`·`Agreement`처럼 이미\n * 있는 컴포넌트를 제품이 슬롯에 넣는다. 화면 전체를 한 컴포넌트로 받으면 제품마다 다른\n * 부분(다에리의 중단 거래 복구, 번뚝의 심사자 폼)을 전부 슬롯으로 되돌려 받아야 해서,\n * 결국 이름만 붙은 Stack이 된다.\n */\nexport type AuthScreenDensity = \"compact\" | \"regular\";\n\nexport type AuthScreenDescriptor = Readonly<{\n /**\n * 세로 공간이 좁은 기기에서 간격을 줄인다. 제품이 화면 높이를 보고 고르며, HJM은\n * 기기 종류를 추측하지 않는다(같은 폭이라도 키보드가 올라오면 좁아진다).\n */\n density?: AuthScreenDensity;\n /** 아래 영역(동의 고지·정책 링크)을 둘지. 계정 생성이 없는 화면은 없을 수 있다. */\n hasFooter?: boolean;\n}>;\n\nexport type ResolvedAuthScreenDescriptor = Readonly<{\n density: AuthScreenDensity;\n hasFooter: boolean;\n /** 위 영역 안쪽 간격(마크→제목→설명→주 행동). */\n heroGap: number;\n /** 위 영역과 주 행동 사이. */\n mainGap: number;\n /** 아래 영역을 띄우는 최소 간격. 화면이 길면 그 이상 벌어진다. */\n footerGap: number;\n paddingInline: number;\n paddingBlock: number;\n maxWidth: number;\n}>;\n\nexport const authScreenDefaults = {\n density: \"regular\",\n hasFooter: true,\n} as const satisfies Required<AuthScreenDescriptor>;\n\n/**\n * 최대 폭 26rem(416)은 `Container`의 reading(720)보다 좁다. 제공자 버튼이 노트북 폭만큼\n * 길어지면 누를 곳이 아니라 띠로 보이기 때문이고, 이 화면의 내용은 버튼 한 줄기뿐이라\n * 읽기 폭을 쓸 이유가 없다.\n */\nexport const authScreenRecipe = {\n slots: [\"root\", \"hero\", \"mark\", \"title\", \"description\", \"main\", \"footer\"] as const,\n defaults: authScreenDefaults,\n maxWidth: 416,\n /** 마크는 아이콘(48)보다 크고 일러스트(120+)보다 작다 — 제품을 알아볼 최소 크기다. */\n markSize: 72,\n markRadius: radius.lg,\n title: typography.titleLarge,\n description: typography.body,\n /** 정책 링크는 글자 높이만큼만 누를 수 있으면 44pt 기준에 못 미친다. */\n footerMinTouchTarget: control.minTouchTarget,\n footerText: typography.caption,\n /** 제공자 버튼 높이는 Provider 계약이 소유한다 — 여기서 다시 정하지 않는다. */\n providerMinHeight: authProviderButtonRecipe.minHeight,\n // 2026-10-01: the action card stays mounted during login so its dimensions and background survive the loader.\n mainCard: { padding: spacing.md, radius: radius.lg },\n densities: {\n regular: {\n heroGap: spacing.md,\n mainGap: spacing.xl,\n footerGap: spacing.xl,\n paddingInline: layout.pagePadding.regular,\n paddingBlock: spacing.xxxl,\n },\n compact: {\n heroGap: spacing.xs,\n mainGap: spacing.md,\n footerGap: spacing.md,\n paddingInline: layout.pagePadding.compact,\n paddingBlock: spacing.lg,\n },\n },\n} as const;\n\nconst densities = new Set<AuthScreenDensity>([\"compact\", \"regular\"]);\n\nexport function validateAuthScreenDescriptor(descriptor: AuthScreenDescriptor): void {\n if (descriptor === null || typeof descriptor !== \"object\") {\n throw new TypeError(\"AuthScreen descriptor must be an object\");\n }\n if (descriptor.density !== undefined && !densities.has(descriptor.density)) {\n throw new TypeError(`Unsupported AuthScreen density: ${String(descriptor.density)}`);\n }\n if (descriptor.hasFooter !== undefined && typeof descriptor.hasFooter !== \"boolean\") {\n throw new TypeError(\"AuthScreen hasFooter must be a boolean\");\n }\n}\n\nexport function resolveAuthScreenDescriptor(\n descriptor: AuthScreenDescriptor = {},\n): ResolvedAuthScreenDescriptor {\n validateAuthScreenDescriptor(descriptor);\n const density = descriptor.density ?? authScreenDefaults.density;\n const scale = authScreenRecipe.densities[density];\n return {\n density,\n hasFooter: descriptor.hasFooter ?? authScreenDefaults.hasFooter,\n heroGap: scale.heroGap,\n mainGap: scale.mainGap,\n footerGap: scale.footerGap,\n paddingInline: scale.paddingInline,\n paddingBlock: scale.paddingBlock,\n maxWidth: authScreenRecipe.maxWidth,\n };\n}\n\nexport const authScreenBehavior = {\n controlled: [],\n inputs: [\"density\", \"hasFooter\"],\n events: [],\n stateAxes: {},\n web: { roles: [\"main\"], keyboard: [], focus: \"none\" },\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"the-hero-and-the-main-action-stay-one-vertically-centred-block\",\n \"the-footer-sits-at-the-bottom-and-never-overlaps-the-main-action\",\n \"content-taller-than-the-viewport-scrolls-instead-of-pushing-the-footer-off-screen\",\n \"the-product-owns-every-string-and-the-mark-asset-while-the-layout-stays-here\",\n \"provider-button-height-comes-from-the-provider-contract-not-from-this-one\",\n \"compact-density-shrinks-gaps-without-changing-the-two-region-structure\",\n \"policy-links-in-the-footer-keep-the-minimum-touch-target\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
|
1
|
+
{"version":3,"file":"auth-screen.js","sourceRoot":"","sources":["../src/auth-screen.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport { authProviderButtonRecipe } from \"./provider-button.js\";\nimport { control, layout, radius, spacing, typography } from \"./foundations.js\";\n\n/**\n * 로그인 화면의 골격이다. 제품이 화면을 다시 조립하지 않게 **배치만** 소유한다.\n *\n * 왜 골격인가: 계정이 있는 제품들이 같은 화면을 각자 만들었고, 같은 실수를 각자 했다.\n * 번뚝은 로고·태그라인·설명 두 줄·동의 고지·정책 링크를 위에서부터 쌓아 제공자 버튼을\n * 화면 밖으로 밀어냈고(2026-09-19 iPhone 17 Pro 실측), 그 배치를 따라한 다른 제품도 같은\n * 순서로 쌓였다. 간격과 최대 폭은 제품마다 다를 이유가 없는데 제품마다 다시 정해졌다.\n *\n * 그래서 **두 영역**만 강제한다. 위 영역(`hero` + `main`)은 남는 세로 공간을 전부 차지하고\n * 그 안에서 가운데 정렬한다. 아래 영역(`footer`)은 바닥에 붙는다. 세 영역 이상으로 나누지\n * 않는 이유는, 셋이 되는 순간 어느 것이 주 행동인지가 배치로 드러나지 않아서다.\n *\n * **HJM이 소유하는 것**: 영역 구분, 간격, 최대 폭, 가운데 정렬, 내용이 길어질 때의 스크롤 전환.\n * **제품이 소유하는 것**: 마크 자산, 모든 문구, 제공자 목록과 순서, 정책 링크의 목적지,\n * 그리고 `main`에 무엇을 넣을지(제공자 버튼만, 또는 가입 재개·심사자 입력 같은 제품 덩어리).\n *\n * 이 계약은 화면을 **그리지 않는다.** `AuthProviderButton`·`BottomInfo`·`Agreement`처럼 이미\n * 있는 컴포넌트를 제품이 슬롯에 넣는다. 화면 전체를 한 컴포넌트로 받으면 제품마다 다른\n * 부분(다에리의 중단 거래 복구, 번뚝의 심사자 폼)을 전부 슬롯으로 되돌려 받아야 해서,\n * 결국 이름만 붙은 Stack이 된다.\n */\nexport type AuthScreenDensity = \"compact\" | \"regular\";\n\nexport type AuthScreenDescriptor = Readonly<{\n /**\n * 세로 공간이 좁은 기기에서 간격을 줄인다. 제품이 화면 높이를 보고 고르며, HJM은\n * 기기 종류를 추측하지 않는다(같은 폭이라도 키보드가 올라오면 좁아진다).\n */\n density?: AuthScreenDensity;\n /** 아래 영역(동의 고지·정책 링크)을 둘지. 계정 생성이 없는 화면은 없을 수 있다. */\n hasFooter?: boolean;\n}>;\n\nexport type ResolvedAuthScreenDescriptor = Readonly<{\n density: AuthScreenDensity;\n hasFooter: boolean;\n /** 위 영역 안쪽 간격(마크→제목→설명→주 행동). */\n heroGap: number;\n /** 위 영역과 주 행동 사이. */\n mainGap: number;\n /** 아래 영역을 띄우는 최소 간격. 화면이 길면 그 이상 벌어진다. */\n footerGap: number;\n paddingInline: number;\n paddingBlock: number;\n maxWidth: number;\n /** 히어로 제목의 글자 단계. `AuthScreenHero`가 이 값으로 그린다. */\n titleVariant: AuthScreenTitleVariant;\n}>;\n\nexport type AuthScreenTitleVariant = \"heading\" | \"titleLarge\";\n\n/**\n * 히어로 문구. 문자열은 전부 제품 소유이고, 글자 단계·색·간격은 `authScreenRecipe.hero`가 소유한다.\n * 마크 자산은 렌더러의 `mark` 슬롯으로 받는다.\n */\nexport type AuthScreenHeroDescriptor = Readonly<{\n title: string;\n eyebrow?: string;\n description?: string;\n /** 문서 구조상의 제목 단계. 로그인 화면이 라우트의 첫 제목이므로 기본은 1이다. */\n headingLevel?: 1 | 2 | 3;\n}>;\n\n/** 정책 링크 한 개. 목적지와 문구는 제품 소유다. */\nexport type AuthScreenPolicyLinkDescriptor = Readonly<{\n label: string;\n /**\n * Supplementary announcement after the label, e.g. the product's localized \"opens in browser\" (Web aria-describedby\n * text / RN accessibilityHint). The visible label stays the link's name; spint dropped its hint when it adopted\n * AuthScreenFooter because the links had no slot for it (2026-10-17).\n */\n accessibilityHint?: string;\n}>;\n\nexport const authScreenDefaults = {\n density: \"regular\",\n hasFooter: true,\n} as const satisfies Required<AuthScreenDescriptor>;\n\n/**\n * 최대 폭 26rem(416)은 `Container`의 reading(720)보다 좁다. 제공자 버튼이 노트북 폭만큼\n * 길어지면 누를 곳이 아니라 띠로 보이기 때문이고, 이 화면의 내용은 버튼 한 줄기뿐이라\n * 읽기 폭을 쓸 이유가 없다.\n */\nexport const authScreenRecipe = {\n slots: [\"root\", \"hero\", \"mark\", \"eyebrow\", \"title\", \"description\", \"main\", \"footer\", \"notice\", \"links\"] as const,\n defaults: authScreenDefaults,\n maxWidth: 416,\n /** 마크는 아이콘(48)보다 크고 일러스트(120+)보다 작다 — 제품을 알아볼 최소 크기다. */\n markSize: 72,\n markRadius: radius.lg,\n /**\n * 히어로 제목은 LS의 태그라인(24)이다. 2026-10-17 사용자 피드백(\"굵기랑 크기 줄여\"): 예제가\n * 화면 첫 제목용 `Top large`(display 32/40)를 히어로에 넣어 402 폭에서 두 줄로 화면을 눌렀다.\n * 로그인 슬로건은 화면 제목이 아니라 제공자 버튼을 소개하는 한 줄이라 display 단계를 쓰지\n * 않고, 굵기는 제목 단계의 medium(500)을 그대로 따른다. compact는 한 단계 낮춘다(densities).\n */\n title: typography.heading,\n description: typography.body,\n hero: {\n eyebrow: { textVariant: \"label\", tone: \"brand\", gapBelow: spacing.xxs },\n title: { tone: \"primary\" },\n /** LS 설명은 보조 글자색이다 — 본문색이면 태그라인과 같은 무게로 읽힌다. */\n description: { textVariant: \"body\", tone: \"muted\", gapAbove: spacing.xs },\n },\n /** 정책 링크는 글자 높이만큼만 누를 수 있으면 44pt 기준에 못 미친다. */\n footerMinTouchTarget: control.minTouchTarget,\n footerText: typography.caption,\n /**\n * 동의 고지와 정책 링크(LS §3: 고지 `caption` 흐린 색, 링크와 gap `xxs`, 링크 `caption` 브랜드색·밑줄,\n * \"개인정보 처리방침 · 이용약관\"). 2026-10-17 사용자 피드백(\"간격 줄여\"): 예제가 ghost Button 두 개를\n * Stack gap 16으로 놓아 버튼 안쪽 여백 12+12가 더해져 글자 사이가 40~42로 벌어졌다. 보이는 간격은\n * `linkGap` + 구분점 + `linkGap` 하나로 정하고, 44 터치 높이는 보이지 않는 히트 영역(Web 높이·가상\n * 요소, Native minHeight·hitSlop)으로 채운다 — 보이는 여백으로 터치 영역을 만들면 간격이 다시 커진다.\n */\n footer: {\n notice: { textVariant: \"caption\", tone: \"muted\" },\n noticeGap: spacing.xxs,\n link: { textVariant: \"caption\", tone: \"brand\", underline: true },\n linkGap: spacing.xs,\n separator: \"·\",\n },\n /** 제공자 버튼 높이는 Provider 계약이 소유한다 — 여기서 다시 정하지 않는다. */\n providerMinHeight: authProviderButtonRecipe.minHeight,\n // 2026-10-01: the action card stays mounted during login so its dimensions and background survive the loader.\n mainCard: { padding: spacing.md, radius: radius.lg },\n densities: {\n regular: {\n heroGap: spacing.md,\n mainGap: spacing.xl,\n footerGap: spacing.xl,\n paddingInline: layout.pagePadding.regular,\n paddingBlock: spacing.xxxl,\n titleVariant: \"heading\",\n },\n compact: {\n heroGap: spacing.xs,\n mainGap: spacing.md,\n footerGap: spacing.md,\n paddingInline: layout.pagePadding.compact,\n paddingBlock: spacing.lg,\n // 세로가 좁은 화면에서는 제목도 한 단계 낮춰 제공자 버튼을 첫 화면에 남긴다.\n titleVariant: \"titleLarge\",\n },\n },\n} as const;\n\nconst densities = new Set<AuthScreenDensity>([\"compact\", \"regular\"]);\n\nexport function validateAuthScreenDescriptor(descriptor: AuthScreenDescriptor): void {\n if (descriptor === null || typeof descriptor !== \"object\") {\n throw new TypeError(\"AuthScreen descriptor must be an object\");\n }\n if (descriptor.density !== undefined && !densities.has(descriptor.density)) {\n throw new TypeError(`Unsupported AuthScreen density: ${String(descriptor.density)}`);\n }\n if (descriptor.hasFooter !== undefined && typeof descriptor.hasFooter !== \"boolean\") {\n throw new TypeError(\"AuthScreen hasFooter must be a boolean\");\n }\n}\n\nfunction assertNonEmpty(value: unknown, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`AuthScreen ${field} must not be empty`);\n }\n}\n\nexport function validateAuthScreenHeroDescriptor(descriptor: AuthScreenHeroDescriptor): void {\n if (descriptor === null || typeof descriptor !== \"object\") {\n throw new TypeError(\"AuthScreen hero must be an object\");\n }\n assertNonEmpty(descriptor.title, \"hero title\");\n if (descriptor.eyebrow !== undefined) assertNonEmpty(descriptor.eyebrow, \"hero eyebrow\");\n if (descriptor.description !== undefined) assertNonEmpty(descriptor.description, \"hero description\");\n if (descriptor.headingLevel !== undefined && ![1, 2, 3].includes(descriptor.headingLevel)) {\n throw new TypeError(`Unsupported AuthScreen hero headingLevel: ${String(descriptor.headingLevel)}`);\n }\n}\n\n/** LS-04: 약관·개인정보처리방침 링크는 로그인 전에 같은 화면에 있어야 하므로 빈 목록을 받지 않는다. */\nexport function validateAuthScreenPolicyLinks(links: readonly AuthScreenPolicyLinkDescriptor[]): void {\n if (!Array.isArray(links) || links.length === 0) {\n throw new TypeError(\"AuthScreen footer needs at least one policy link\");\n }\n for (const link of links) {\n assertNonEmpty(link?.label, \"policy link label\");\n if (link.accessibilityHint !== undefined) assertNonEmpty(link.accessibilityHint, \"policy link accessibilityHint\");\n }\n}\n\nexport function resolveAuthScreenDescriptor(\n descriptor: AuthScreenDescriptor = {},\n): ResolvedAuthScreenDescriptor {\n validateAuthScreenDescriptor(descriptor);\n const density = descriptor.density ?? authScreenDefaults.density;\n const scale = authScreenRecipe.densities[density];\n return {\n density,\n hasFooter: descriptor.hasFooter ?? authScreenDefaults.hasFooter,\n heroGap: scale.heroGap,\n mainGap: scale.mainGap,\n footerGap: scale.footerGap,\n paddingInline: scale.paddingInline,\n paddingBlock: scale.paddingBlock,\n maxWidth: authScreenRecipe.maxWidth,\n titleVariant: scale.titleVariant,\n };\n}\n\nexport const authScreenBehavior = {\n controlled: [],\n inputs: [\"density\", \"hasFooter\"],\n events: [],\n stateAxes: {},\n web: { roles: [\"main\"], keyboard: [], focus: \"none\" },\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"the-hero-and-the-main-action-stay-one-vertically-centred-block\",\n \"the-footer-sits-at-the-bottom-and-never-overlaps-the-main-action\",\n \"content-taller-than-the-viewport-scrolls-instead-of-pushing-the-footer-off-screen\",\n \"the-product-owns-every-string-and-the-mark-asset-while-the-layout-stays-here\",\n \"provider-button-height-comes-from-the-provider-contract-not-from-this-one\",\n \"compact-density-shrinks-gaps-without-changing-the-two-region-structure\",\n \"policy-links-in-the-footer-keep-the-minimum-touch-target\",\n \"the-hero-title-uses-the-recipe-title-step-not-a-display-heading\",\n \"policy-links-are-separated-by-one-recipe-gap-and-a-dot-while-the-touch-area-stays-invisible\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
package/dist/base-recipes.d.ts
CHANGED
package/dist/base-recipes.js
CHANGED
package/dist/base-recipes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-recipes.js","sourceRoot":"","sources":["../src/base-recipes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThemeColors } from \"./colors.js\";\nimport { control, fontWeight, radius, spacing, typography } from \"./foundations.js\";\nimport type { FontWeightValue, TextVariant } from \"./foundations.js\";\n\nexport type ButtonTone = \"primary\" | \"secondary\" | \"ghost\" | \"danger\" | \"link\";\n/** Corner geometry of the control frame. Mirrors `IconButtonShape`. */\nexport type ButtonShape = \"rounded\" | \"pill\";\n/**\n * Where the label sits inside the frame. `center` is the action default;\n * `leading` is for a full-width row action whose label reads as list copy.\n */\nexport type ButtonAlign = \"center\" | \"leading\";\nexport type ButtonSize = keyof typeof control.buttonHeight;\nexport type SurfaceTone = \"default\" | \"raised\" | \"accent\" | \"sunken\" | \"subtle\";\nexport type SurfacePadding = \"none\" | keyof typeof spacing;\nexport type SurfaceRadius = keyof typeof radius;\nexport type FieldVariant = \"surface\" | \"inset\";\nexport type FieldShape = \"medium\" | \"large\" | \"full\";\n\n/** Small renderer entry point for the three foundational visual recipes. */\nexport const buttonRecipe = {\n slots: [\"root\", \"leading\", \"label\", \"trailing\", \"spinner\"] as const,\n defaults: { tone: \"primary\", size: \"medium\", shape: \"rounded\", align: \"center\" } as const,\n tones: {\n primary: { background: \"primary\", content: \"onPrimary\", border: null, paddingHorizontal: null },\n // The outline is a border role, not a text color. Drawing it in `textSub`\n // made a resting control read heavier than the selected one beside it.\n secondary: { background: \"bg\", content: \"text\", border: \"borderControl\", paddingHorizontal: null },\n ghost: { background: null, content: \"textMuted\", border: null, paddingHorizontal: null },\n danger: { background: \"dangerFill\", content: \"onDanger\", border: null, paddingHorizontal: null },\n // A link-tone control is inline copy, so the size axis' horizontal padding\n // would push it out of alignment with the text around it.\n link: { background: null, content: \"contentBrand\", border: null, paddingHorizontal: 0 },\n },\n /**\n * Visual treatment for a control that is also a toggle. `accessibilityState`\n * / `aria-pressed` already expressed the state; without a paired visual every\n * consumer painted the selected background in product styles.\n */\n states: {\n // Selected reactions use content color, without adding an outline (2026-10-10 user request).\n selected: { background: \"bg\", content: \"contentBrand\", border: null },\n },\n sizes: {\n small: {\n height: control.buttonHeight.small,\n hitSlop: control.buttonHitSlop.small,\n paddingHorizontal: spacing.sm,\n textVariant: \"label\",\n },\n medium: {\n height: control.buttonHeight.medium,\n hitSlop: control.buttonHitSlop.medium,\n paddingHorizontal: spacing.md,\n textVariant: \"body\",\n },\n large: {\n height: control.buttonHeight.large,\n hitSlop: control.buttonHitSlop.large,\n paddingHorizontal: spacing.lg,\n textVariant: \"bodyLarge\",\n },\n },\n shapes: { rounded: \"md\", pill: \"full\" },\n aligns: { center: \"center\", leading: \"flex-start\" },\n opacity: { disabled: 0.5, pressed: 0.86 },\n /**\n * 긴 라벨의 처리 규칙. 지금까지 이 판단이 없어서 Web은 무한히 줄바꿈하고 RN은 한 줄로\n * 잘렸다 — 같은 번역문이 두 표면에서 다르게 보였다.\n *\n * 자르지 않고 **두 줄까지 접는다**. 잘린 행동 라벨(\"삭제하...\"))은 무엇을 하는 버튼인지\n * 잃는다. 대신 무제한 줄바꿈도 막는다 — 하단 CTA 행처럼 버튼이 남의 레이아웃을 밀어내는\n * 자리가 있고, 두 줄을 넘는 라벨은 레이아웃이 아니라 카피의 문제다.\n *\n * 큰 글자 설정에서는 상한을 **푼다**. 사용자가 키운 글자에서 두 줄로 자르면 내용이\n * 사라지고, 그건 WCAG 1.4.4를 어긴다. 제품 카피가 아니라 사용자 설정이 원인이기 때문에\n * 같은 규칙을 적용할 수 없다.\n */\n label: { overflow: \"wrap\", maxLines: 2, liftCapOnLargeText: true },\n} as const satisfies {\n slots: readonly string[];\n defaults: { tone: ButtonTone; size: ButtonSize; shape: ButtonShape; align: ButtonAlign };\n tones: Record<\n ButtonTone,\n {\n background: keyof ThemeColors | null;\n content: keyof ThemeColors;\n border: keyof ThemeColors | null;\n /** `null` keeps the size axis' padding. */\n paddingHorizontal: number | null;\n }\n >;\n states: {\n selected: {\n background: keyof ThemeColors;\n content: keyof ThemeColors;\n border: keyof ThemeColors | null;\n };\n };\n sizes: Record<\n ButtonSize,\n {\n height: number;\n hitSlop: number;\n paddingHorizontal: number;\n textVariant: TextVariant;\n }\n >;\n shapes: Record<ButtonShape, keyof typeof radius>;\n aligns: Record<ButtonAlign, \"center\" | \"flex-start\">;\n opacity: { disabled: number; pressed: number };\n label: { overflow: \"wrap\"; maxLines: number; liftCapOnLargeText: boolean };\n};\n\n/**\n * Edge alignment for a ghost text Button at a row/header edge (2026-10-17 measured audit: a sort trigger's chevron\n * sat 29px from the edge against a 16px inset). Same rule as `resolveIconButtonEdgeOverhang`: a frameless control\n * overhangs the edge by its own inline padding so its label/glyph, not the transparent box, sets the inset.\n * Framed tones (primary, secondary, danger) align their frame instead, and `link` already has no padding: 0.\n */\nexport type ButtonEdge = \"start\" | \"end\" | \"both\";\nexport function resolveButtonEdgeOverhang(tone: ButtonTone, size: ButtonSize): number {\n const toneContract = buttonRecipe.tones[tone];\n if (toneContract.background !== null || toneContract.border !== null) return 0;\n return toneContract.paddingHorizontal ?? buttonRecipe.sizes[size].paddingHorizontal;\n}\n\n/** Resolves the label's line cap for one resolved environment. `null` means uncapped. */\nexport function resolveButtonLabelLines(largeText: boolean): number | null {\n return largeText && buttonRecipe.label.liftCapOnLargeText ? null : buttonRecipe.label.maxLines;\n}\n\nexport const surfaceRecipe = {\n default: {\n background: \"bg\",\n border: \"border\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: true,\n clipsContent: true,\n },\n raised: {\n background: \"bg\",\n border: \"border\",\n borderAlpha: 1,\n elevated: true,\n borderAlways: true,\n // An elevated surface must not clip: `overflow: hidden` cuts off its own\n // shadow. Every other tone clips so a child image cannot spill past the\n // rounded corner, which consumers were fixing in product styles.\n clipsContent: false,\n },\n // White surfaces retain a boundary; tonal fills were removed at the user\n // review, so elevation and border now carry hierarchy.\n sunken: {\n background: \"bg\",\n border: \"border\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: true,\n clipsContent: true,\n },\n accent: {\n background: \"bg\",\n border: \"primary\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: true,\n clipsContent: true,\n },\n subtle: {\n background: \"bg\",\n border: \"border\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: true,\n clipsContent: true,\n },\n} as const satisfies Record<\n SurfaceTone,\n {\n background: keyof ThemeColors;\n border: keyof ThemeColors;\n borderAlpha: number;\n elevated: boolean;\n clipsContent: boolean;\n borderAlways: boolean;\n }\n>;\n\n/** Text placement inside a field control. */\nexport type FieldAlign = \"start\" | \"center\";\n\nexport const fieldRecipe = {\n slots: [\n \"root\",\n \"label\",\n \"control\",\n \"leading\",\n \"input\",\n \"trailing\",\n \"hint\",\n \"error\",\n ] as const,\n defaults: { variant: \"surface\", shape: \"medium\", align: \"start\" } as const,\n variants: {\n // Input interiors stay on the canvas; focus is conveyed by the blue outline, not a grey or blue fill.\n surface: { background: \"bg\" },\n inset: { background: \"bg\" },\n },\n shapes: {\n medium: \"md\",\n large: \"lg\",\n full: \"full\",\n },\n states: {\n idle: { border: \"borderControl\" },\n focused: { border: \"contentBrand\" },\n invalid: { border: \"danger\" },\n },\n minHeight: control.minTouchTarget,\n multilineMinHeight: 80,\n /**\n * Upper bound for a growing multiline field, expressed in visible lines. A\n * consumer otherwise reaches for `inputStyle={{ maxHeight }}`, which moves a\n * recipe-owned dimension into product code. `null` keeps unbounded growth.\n */\n multilineMaxVisibleLines: null as number | null,\n borderWidth: 1,\n focusRingWidth: 2,\n focusRingOffset: 2,\n paddingHorizontal: spacing.md,\n paddingVertical: spacing.sm,\n textVariant: \"body\",\n label: {\n color: \"textBody\",\n textVariant: \"body\",\n fontWeight: fontWeight.semibold,\n gap: spacing.xs,\n },\n support: {\n hintColor: \"textMuted\",\n errorColor: \"danger\",\n textVariant: \"label\",\n gap: 6,\n },\n placeholder: { color: \"textMuted\" },\n disabledOpacity: 0.6,\n /**\n * Which slots `disabledOpacity` fades (2026-10-06). Label and control dim; the\n * hint and the error keep their contrast, because they are the text that says\n * why the field is locked or what to fix. Renderers used to fade the whole\n * frame, so the small support text (`support.textVariant`) — already the\n * lowest-contrast text in the field — lost contrast too. Fading nothing was\n * rejected: the label and the control are what tell the user the field is off.\n *\n * Every field-family input follows this scope. The amount is the component\n * recipe's own `states.disabledOpacity` where it has one (SearchField,\n * PasswordField, OtpField, NumberField, Select); otherwise this recipe's\n * `disabledOpacity` (TextField, TextArea, Field, Combobox, DatePicker, TagsInput).\n */\n disabledScope: {\n dimmed: [\"label\", \"control\"],\n unchanged: [\"hint\", \"error\"],\n },\n} as const satisfies {\n slots: readonly string[];\n defaults: { variant: FieldVariant; shape: FieldShape; align: FieldAlign };\n variants: Record<FieldVariant, { background: keyof ThemeColors }>;\n shapes: Record<FieldShape, keyof typeof radius>;\n states: Record<\n \"idle\" | \"focused\" | \"invalid\",\n { border: keyof ThemeColors }\n >;\n minHeight: number;\n multilineMinHeight: number;\n multilineMaxVisibleLines: number | null;\n borderWidth: number;\n focusRingWidth: number;\n focusRingOffset: number;\n paddingHorizontal: number;\n paddingVertical: number;\n textVariant: keyof typeof typography;\n label: {\n color: keyof ThemeColors;\n textVariant: keyof typeof typography;\n fontWeight: FontWeightValue;\n gap: number;\n };\n support: {\n hintColor: keyof ThemeColors;\n errorColor: keyof ThemeColors;\n textVariant: keyof typeof typography;\n gap: number;\n };\n placeholder: { color: keyof ThemeColors };\n disabledOpacity: number;\n disabledScope: {\n dimmed: readonly (\"label\" | \"control\")[];\n unchanged: readonly (\"hint\" | \"error\")[];\n };\n};\n\n/**\n * Renderer-neutral Surface geometry. Tone colors stay in `surfaceRecipe` for\n * compatibility; these axes make the previously implicit Web/Native defaults\n * explicit without changing that public recipe shape.\n */\nexport const surfaceDefaults = {\n tone: \"default\",\n padding: \"none\",\n radius: \"lg\",\n bordered: false,\n} as const satisfies {\n tone: SurfaceTone;\n padding: SurfacePadding;\n radius: SurfaceRadius;\n bordered: boolean;\n};\n\nexport const surfaceGeometry = {\n paddings: { none: 0, ...spacing },\n radii: radius,\n} as const satisfies {\n paddings: Record<SurfacePadding, number>;\n radii: Record<SurfaceRadius, number>;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"base-recipes.js","sourceRoot":"","sources":["../src/base-recipes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThemeColors } from \"./colors.js\";\nimport { control, fontWeight, radius, spacing, typography } from \"./foundations.js\";\nimport type { FontWeightValue, TextVariant } from \"./foundations.js\";\n\nexport type ButtonTone = \"primary\" | \"secondary\" | \"ghost\" | \"danger\" | \"link\";\n/** Corner geometry of the control frame. Mirrors `IconButtonShape`. */\nexport type ButtonShape = \"rounded\" | \"pill\";\n/**\n * Where the label sits inside the frame. `center` is the action default;\n * `leading` is for a full-width row action whose label reads as list copy.\n */\nexport type ButtonAlign = \"center\" | \"leading\";\nexport type ButtonSize = keyof typeof control.buttonHeight;\nexport type SurfaceTone = \"default\" | \"raised\" | \"accent\" | \"sunken\" | \"subtle\";\nexport type SurfacePadding = \"none\" | keyof typeof spacing;\nexport type SurfaceRadius = keyof typeof radius;\nexport type FieldVariant = \"surface\" | \"inset\";\nexport type FieldShape = \"medium\" | \"large\" | \"full\";\n\n/** Small renderer entry point for the three foundational visual recipes. */\nexport const buttonRecipe = {\n slots: [\"root\", \"leading\", \"label\", \"trailing\", \"spinner\"] as const,\n defaults: { tone: \"primary\", size: \"medium\", shape: \"rounded\", align: \"center\" } as const,\n tones: {\n primary: { background: \"primary\", content: \"onPrimary\", border: null, paddingHorizontal: null },\n // The outline is a border role, not a text color. Drawing it in `textSub`\n // made a resting control read heavier than the selected one beside it.\n secondary: { background: \"bg\", content: \"text\", border: \"borderControl\", paddingHorizontal: null },\n ghost: { background: null, content: \"textMuted\", border: null, paddingHorizontal: null },\n danger: { background: \"dangerFill\", content: \"onDanger\", border: null, paddingHorizontal: null },\n // A link-tone control is inline copy, so the size axis' horizontal padding\n // would push it out of alignment with the text around it.\n link: { background: null, content: \"contentBrand\", border: null, paddingHorizontal: 0 },\n },\n /**\n * Visual treatment for a control that is also a toggle. `accessibilityState`\n * / `aria-pressed` already expressed the state; without a paired visual every\n * consumer painted the selected background in product styles.\n */\n states: {\n // Selected reactions use content color, without adding an outline (2026-10-10 user request).\n selected: { background: \"bg\", content: \"contentBrand\", border: null },\n },\n sizes: {\n small: {\n height: control.buttonHeight.small,\n hitSlop: control.buttonHitSlop.small,\n paddingHorizontal: spacing.sm,\n textVariant: \"label\",\n },\n medium: {\n height: control.buttonHeight.medium,\n hitSlop: control.buttonHitSlop.medium,\n paddingHorizontal: spacing.md,\n textVariant: \"body\",\n },\n large: {\n height: control.buttonHeight.large,\n hitSlop: control.buttonHitSlop.large,\n paddingHorizontal: spacing.lg,\n textVariant: \"bodyLarge\",\n },\n },\n shapes: { rounded: \"md\", pill: \"full\" },\n aligns: { center: \"center\", leading: \"flex-start\" },\n opacity: { disabled: 0.5, pressed: 0.86 },\n /**\n * 긴 라벨의 처리 규칙. 지금까지 이 판단이 없어서 Web은 무한히 줄바꿈하고 RN은 한 줄로\n * 잘렸다 — 같은 번역문이 두 표면에서 다르게 보였다.\n *\n * 자르지 않고 **두 줄까지 접는다**. 잘린 행동 라벨(\"삭제하...\"))은 무엇을 하는 버튼인지\n * 잃는다. 대신 무제한 줄바꿈도 막는다 — 하단 CTA 행처럼 버튼이 남의 레이아웃을 밀어내는\n * 자리가 있고, 두 줄을 넘는 라벨은 레이아웃이 아니라 카피의 문제다.\n *\n * 큰 글자 설정에서는 상한을 **푼다**. 사용자가 키운 글자에서 두 줄로 자르면 내용이\n * 사라지고, 그건 WCAG 1.4.4를 어긴다. 제품 카피가 아니라 사용자 설정이 원인이기 때문에\n * 같은 규칙을 적용할 수 없다.\n */\n label: { overflow: \"wrap\", maxLines: 2, liftCapOnLargeText: true },\n} as const satisfies {\n slots: readonly string[];\n defaults: { tone: ButtonTone; size: ButtonSize; shape: ButtonShape; align: ButtonAlign };\n tones: Record<\n ButtonTone,\n {\n background: keyof ThemeColors | null;\n content: keyof ThemeColors;\n border: keyof ThemeColors | null;\n /** `null` keeps the size axis' padding. */\n paddingHorizontal: number | null;\n }\n >;\n states: {\n selected: {\n background: keyof ThemeColors;\n content: keyof ThemeColors;\n border: keyof ThemeColors | null;\n };\n };\n sizes: Record<\n ButtonSize,\n {\n height: number;\n hitSlop: number;\n paddingHorizontal: number;\n textVariant: TextVariant;\n }\n >;\n shapes: Record<ButtonShape, keyof typeof radius>;\n aligns: Record<ButtonAlign, \"center\" | \"flex-start\">;\n opacity: { disabled: number; pressed: number };\n label: { overflow: \"wrap\"; maxLines: number; liftCapOnLargeText: boolean };\n};\n\n/**\n * Edge alignment for a ghost text Button at a row/header edge (2026-10-17 measured audit: a sort trigger's chevron\n * sat 29px from the edge against a 16px inset). Same rule as `resolveIconButtonEdgeOverhang`: a frameless control\n * overhangs the edge by its own inline padding so its label/glyph, not the transparent box, sets the inset.\n * Framed tones (primary, secondary, danger) align their frame instead, and `link` already has no padding: 0.\n */\nexport type ButtonEdge = \"start\" | \"end\" | \"both\";\nexport function resolveButtonEdgeOverhang(tone: ButtonTone, size: ButtonSize): number {\n const toneContract = buttonRecipe.tones[tone];\n if (toneContract.background !== null || toneContract.border !== null) return 0;\n return toneContract.paddingHorizontal ?? buttonRecipe.sizes[size].paddingHorizontal;\n}\n\n/** Resolves the label's line cap for one resolved environment. `null` means uncapped. */\nexport function resolveButtonLabelLines(largeText: boolean): number | null {\n return largeText && buttonRecipe.label.liftCapOnLargeText ? null : buttonRecipe.label.maxLines;\n}\n\nexport const surfaceRecipe = {\n default: {\n background: \"bg\",\n border: \"border\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: true,\n clipsContent: true,\n },\n raised: {\n background: \"bg\",\n border: \"border\",\n borderAlpha: 1,\n elevated: true,\n borderAlways: true,\n // An elevated surface must not clip: `overflow: hidden` cuts off its own\n // shadow. Every other tone clips so a child image cannot spill past the\n // rounded corner, which consumers were fixing in product styles.\n clipsContent: false,\n },\n // White surfaces retain a boundary; tonal fills were removed at the user\n // review, so elevation and border now carry hierarchy.\n sunken: {\n background: \"bg\",\n border: \"border\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: true,\n clipsContent: true,\n },\n accent: {\n background: \"bg\",\n border: \"primary\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: true,\n clipsContent: true,\n },\n subtle: {\n background: \"bg\",\n border: \"border\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: true,\n clipsContent: true,\n },\n} as const satisfies Record<\n SurfaceTone,\n {\n background: keyof ThemeColors;\n border: keyof ThemeColors;\n borderAlpha: number;\n elevated: boolean;\n clipsContent: boolean;\n borderAlways: boolean;\n }\n>;\n\n/** Text placement inside a field control. */\nexport type FieldAlign = \"start\" | \"center\";\n\nexport const fieldRecipe = {\n slots: [\n \"root\",\n \"label\",\n \"control\",\n \"leading\",\n \"input\",\n \"trailing\",\n \"hint\",\n \"error\",\n ] as const,\n defaults: { variant: \"surface\", shape: \"medium\", align: \"start\" } as const,\n variants: {\n // Input interiors stay on the canvas; focus is conveyed by the blue outline, not a grey or blue fill.\n surface: { background: \"bg\" },\n inset: { background: \"bg\" },\n },\n shapes: {\n medium: \"md\",\n large: \"lg\",\n full: \"full\",\n },\n states: {\n idle: { border: \"borderControl\" },\n focused: { border: \"contentBrand\" },\n invalid: { border: \"danger\" },\n },\n minHeight: control.minTouchTarget,\n multilineMinHeight: 80,\n /**\n * Upper bound for a growing multiline field, expressed in visible lines. A\n * consumer otherwise reaches for `inputStyle={{ maxHeight }}`, which moves a\n * recipe-owned dimension into product code. `null` keeps unbounded growth.\n */\n multilineMaxVisibleLines: null as number | null,\n borderWidth: 1,\n focusRingWidth: 2,\n focusRingOffset: 2,\n paddingHorizontal: spacing.md,\n paddingVertical: spacing.sm,\n textVariant: \"body\",\n label: {\n color: \"textBody\",\n textVariant: \"body\",\n fontWeight: fontWeight.medium,\n gap: spacing.xs,\n },\n support: {\n hintColor: \"textMuted\",\n errorColor: \"danger\",\n textVariant: \"label\",\n gap: 6,\n },\n placeholder: { color: \"textMuted\" },\n disabledOpacity: 0.6,\n /**\n * Which slots `disabledOpacity` fades (2026-10-06). Label and control dim; the\n * hint and the error keep their contrast, because they are the text that says\n * why the field is locked or what to fix. Renderers used to fade the whole\n * frame, so the small support text (`support.textVariant`) — already the\n * lowest-contrast text in the field — lost contrast too. Fading nothing was\n * rejected: the label and the control are what tell the user the field is off.\n *\n * Every field-family input follows this scope. The amount is the component\n * recipe's own `states.disabledOpacity` where it has one (SearchField,\n * PasswordField, OtpField, NumberField, Select); otherwise this recipe's\n * `disabledOpacity` (TextField, TextArea, Field, Combobox, DatePicker, TagsInput).\n */\n disabledScope: {\n dimmed: [\"label\", \"control\"],\n unchanged: [\"hint\", \"error\"],\n },\n} as const satisfies {\n slots: readonly string[];\n defaults: { variant: FieldVariant; shape: FieldShape; align: FieldAlign };\n variants: Record<FieldVariant, { background: keyof ThemeColors }>;\n shapes: Record<FieldShape, keyof typeof radius>;\n states: Record<\n \"idle\" | \"focused\" | \"invalid\",\n { border: keyof ThemeColors }\n >;\n minHeight: number;\n multilineMinHeight: number;\n multilineMaxVisibleLines: number | null;\n borderWidth: number;\n focusRingWidth: number;\n focusRingOffset: number;\n paddingHorizontal: number;\n paddingVertical: number;\n textVariant: keyof typeof typography;\n label: {\n color: keyof ThemeColors;\n textVariant: keyof typeof typography;\n fontWeight: FontWeightValue;\n gap: number;\n };\n support: {\n hintColor: keyof ThemeColors;\n errorColor: keyof ThemeColors;\n textVariant: keyof typeof typography;\n gap: number;\n };\n placeholder: { color: keyof ThemeColors };\n disabledOpacity: number;\n disabledScope: {\n dimmed: readonly (\"label\" | \"control\")[];\n unchanged: readonly (\"hint\" | \"error\")[];\n };\n};\n\n/**\n * Renderer-neutral Surface geometry. Tone colors stay in `surfaceRecipe` for\n * compatibility; these axes make the previously implicit Web/Native defaults\n * explicit without changing that public recipe shape.\n */\nexport const surfaceDefaults = {\n tone: \"default\",\n padding: \"none\",\n radius: \"lg\",\n bordered: false,\n} as const satisfies {\n tone: SurfaceTone;\n padding: SurfacePadding;\n radius: SurfaceRadius;\n bordered: boolean;\n};\n\nexport const surfaceGeometry = {\n paddings: { none: 0, ...spacing },\n radii: radius,\n} as const satisfies {\n paddings: Record<SurfacePadding, number>;\n radii: Record<SurfaceRadius, number>;\n};\n"]}
|
package/dist/behaviors.d.ts
CHANGED
|
@@ -775,7 +775,7 @@ export declare const behaviorRegistry: {
|
|
|
775
775
|
readonly states: readonly [];
|
|
776
776
|
readonly actions: readonly [];
|
|
777
777
|
};
|
|
778
|
-
readonly scenarios: readonly ["the-hero-and-the-main-action-stay-one-vertically-centred-block", "the-footer-sits-at-the-bottom-and-never-overlaps-the-main-action", "content-taller-than-the-viewport-scrolls-instead-of-pushing-the-footer-off-screen", "the-product-owns-every-string-and-the-mark-asset-while-the-layout-stays-here", "provider-button-height-comes-from-the-provider-contract-not-from-this-one", "compact-density-shrinks-gaps-without-changing-the-two-region-structure", "policy-links-in-the-footer-keep-the-minimum-touch-target"];
|
|
778
|
+
readonly scenarios: readonly ["the-hero-and-the-main-action-stay-one-vertically-centred-block", "the-footer-sits-at-the-bottom-and-never-overlaps-the-main-action", "content-taller-than-the-viewport-scrolls-instead-of-pushing-the-footer-off-screen", "the-product-owns-every-string-and-the-mark-asset-while-the-layout-stays-here", "provider-button-height-comes-from-the-provider-contract-not-from-this-one", "compact-density-shrinks-gaps-without-changing-the-two-region-structure", "policy-links-in-the-footer-keep-the-minimum-touch-target", "the-hero-title-uses-the-recipe-title-step-not-a-display-heading", "policy-links-are-separated-by-one-recipe-gap-and-a-dot-while-the-touch-area-stays-invisible"];
|
|
779
779
|
};
|
|
780
780
|
readonly heading: {
|
|
781
781
|
readonly controlled: readonly [];
|
package/dist/breadcrumb.d.ts
CHANGED
package/dist/breadcrumb.js
CHANGED
package/dist/breadcrumb.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumb.js","sourceRoot":"","sources":["../src/breadcrumb.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"breadcrumb.js","sourceRoot":"","sources":["../src/breadcrumb.ts"],"names":[],"mappings":"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","sourcesContent":["import { validateLinkDestination, type LinkDestination } from \"./link.js\";\nimport { fontWeight, spacing } from \"./foundations.js\";\nimport { focusIndicatorContract } from \"./component-contracts.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * Every ancestor crumb is a destination; the final crumb is the current page\n * and never carries one. Position decides which crumb is current, so the\n * discriminant is enforced by `validateBreadcrumbDescriptor`, not the type.\n */\nexport type BreadcrumbItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n destination?: LinkDestination;\n}>;\n\nexport type BreadcrumbDescriptor<Id extends string = string> = Readonly<{\n items: readonly BreadcrumbItemDescriptor<Id>[];\n}>;\n\nexport type ResolvedBreadcrumbItemDescriptor<Id extends string = string> =\n | Readonly<{ id: Id; label: string; current: false; destination: LinkDestination }>\n | Readonly<{ id: Id; label: string; current: true }>;\n\nexport type ResolvedBreadcrumbDescriptor<Id extends string = string> = Readonly<{\n items: readonly ResolvedBreadcrumbItemDescriptor<Id>[];\n}>;\n\nfunction assertCopy(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Breadcrumb ${field} must not be empty`);\n }\n}\n\nexport function validateBreadcrumbDescriptor<Id extends string>(\n descriptor: BreadcrumbDescriptor<Id>,\n): void {\n if (descriptor.items.length === 0) {\n throw new RangeError(\"Breadcrumb must contain at least one item\");\n }\n const ids = new Set<Id>();\n const lastIndex = descriptor.items.length - 1;\n descriptor.items.forEach((item, index) => {\n assertCopy(item.id, \"id\");\n if (ids.has(item.id)) {\n throw new TypeError(`Duplicate Breadcrumb id: ${item.id}`);\n }\n ids.add(item.id);\n assertCopy(item.label, \"label\");\n if (index === lastIndex) {\n if (item.destination !== undefined) {\n throw new TypeError(\n `Current Breadcrumb item ${item.id} must not have a destination`,\n );\n }\n return;\n }\n if (item.destination === undefined) {\n throw new TypeError(`Breadcrumb item ${item.id} needs a destination`);\n }\n validateLinkDestination(item.destination);\n });\n}\n\nexport function resolveBreadcrumbDescriptor<Id extends string>(\n descriptor: BreadcrumbDescriptor<Id>,\n): ResolvedBreadcrumbDescriptor<Id> {\n validateBreadcrumbDescriptor(descriptor);\n const lastIndex = descriptor.items.length - 1;\n return {\n items: descriptor.items.map((item, index) =>\n index === lastIndex\n ? { id: item.id, label: item.label, current: true as const }\n : {\n id: item.id,\n label: item.label,\n current: false as const,\n destination: item.destination as LinkDestination,\n },\n ),\n };\n}\n\n/**\n * Crumbs read as an inline trail, not standalone buttons, so this mirrors\n * Link's inline treatment (underline + focus indicator, no forced 44-unit\n * row height) rather than the collection-item or field-frame grammar.\n */\nexport const breadcrumbRecipe = {\n slots: [\"root\", \"list\", \"item\", \"link\", \"current\", \"separator\"] as const,\n gap: spacing.xxs,\n link: {\n color: semanticColors.content.secondary,\n textVariant: \"label\",\n focus: focusIndicatorContract,\n },\n current: {\n color: semanticColors.content.primary,\n textVariant: \"label\",\n fontWeight: fontWeight.medium,\n },\n separator: {\n color: semanticColors.content.decorative,\n glyph: \"xs\",\n icon: \"chevronEnd\",\n decorative: true,\n },\n} as const;\n\n/**\n * Native has no equivalent surface: the platform back gesture and TopBar\n * title already own \"where am I / how do I go back\". `web.roles` layers a\n * `navigation` landmark and `list`/`listitem` grouping on top of the plain\n * `link` behavior each ancestor crumb already gets from `Link`.\n */\nexport const breadcrumbBehaviorSpec = {\n controlled: [],\n inputs: [\"items\"],\n stateAxes: {},\n web: {\n roles: [\"navigation\", \"list\", \"listitem\", \"link\"],\n keyboard: [\"Tab\", \"Enter\"],\n focus: \"native\",\n },\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"only-the-last-item-is-current-and-has-no-destination\",\n \"every-ancestor-item-reuses-the-link-destination-contract\",\n \"current-item-is-marked-aria-current-page-and-is-not-a-tab-stop\",\n \"separators-are-decorative-and-excluded-from-the-accessibility-tree\",\n \"duplicate-or-empty-item-identity-is-rejected-before-render\",\n \"no-automatic-truncation-collapses-items\",\n ],\n} as const;\n"]}
|
package/dist/calendar.d.ts
CHANGED
|
@@ -171,7 +171,7 @@ export declare const calendarRecipe: {
|
|
|
171
171
|
readonly gap: 8;
|
|
172
172
|
readonly monthLabel: {
|
|
173
173
|
readonly textVariant: "title";
|
|
174
|
-
readonly fontWeight: "
|
|
174
|
+
readonly fontWeight: "500";
|
|
175
175
|
readonly color: Readonly<{
|
|
176
176
|
source: "theme";
|
|
177
177
|
key: "text";
|
package/dist/calendar.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { control,
|
|
1
|
+
import { control, glyph, layout, opacity, spacing, stroke, typography } from "./foundations.js";
|
|
2
2
|
import { focusIndicatorContract } from "./component-contracts.js";
|
|
3
3
|
import { semanticColors } from "./semantic-colors.js";
|
|
4
4
|
const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -214,7 +214,7 @@ export const calendarRecipe = {
|
|
|
214
214
|
gap: spacing.xs,
|
|
215
215
|
monthLabel: {
|
|
216
216
|
textVariant: "title",
|
|
217
|
-
fontWeight: fontWeight
|
|
217
|
+
fontWeight: typography.title.fontWeight,
|
|
218
218
|
color: semanticColors.content.primary,
|
|
219
219
|
},
|
|
220
220
|
navButton: { diameter: control.buttonHeight.medium, color: semanticColors.content.secondary },
|