@hjmds/design-contracts 1.1.1 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agreement.d.ts +183 -0
- package/dist/agreement.d.ts.map +1 -0
- package/dist/agreement.js +137 -0
- package/dist/agreement.js.map +1 -0
- package/dist/anchor.d.ts +62 -0
- package/dist/anchor.d.ts.map +1 -0
- package/dist/anchor.js +43 -0
- package/dist/anchor.js.map +1 -0
- package/dist/asset.d.ts +94 -0
- package/dist/asset.d.ts.map +1 -0
- package/dist/asset.js +60 -0
- package/dist/asset.js.map +1 -0
- package/dist/auth-screen.d.ts +119 -0
- package/dist/auth-screen.d.ts.map +1 -0
- package/dist/auth-screen.js +87 -0
- package/dist/auth-screen.js.map +1 -0
- package/dist/base-recipes.d.ts +19 -0
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +17 -0
- package/dist/base-recipes.js.map +1 -1
- package/dist/behaviors.d.ts +363 -3
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/behaviors.js +37 -1
- package/dist/behaviors.js.map +1 -1
- package/dist/bottom-info.d.ts +58 -0
- package/dist/bottom-info.d.ts.map +1 -0
- package/dist/bottom-info.js +48 -0
- package/dist/bottom-info.js.map +1 -0
- package/dist/carousel.d.ts +3 -3
- package/dist/carousel.js +1 -1
- package/dist/carousel.js.map +1 -1
- package/dist/catalog.d.ts +1315 -245
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +67 -25
- package/dist/catalog.js.map +1 -1
- package/dist/collapsible.d.ts +56 -0
- package/dist/collapsible.d.ts.map +1 -0
- package/dist/collapsible.js +37 -0
- package/dist/collapsible.js.map +1 -0
- package/dist/component-definitions.d.ts +17 -0
- package/dist/component-definitions.d.ts.map +1 -1
- package/dist/component-definitions.js +17 -0
- package/dist/component-definitions.js.map +1 -1
- package/dist/component-recipes.d.ts +20 -3
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +24 -1
- package/dist/component-recipes.js.map +1 -1
- package/dist/context-menu.d.ts +53 -0
- package/dist/context-menu.d.ts.map +1 -0
- package/dist/context-menu.js +44 -0
- package/dist/context-menu.js.map +1 -0
- package/dist/counter-badge-recipe.d.ts +5 -0
- package/dist/counter-badge-recipe.d.ts.map +1 -1
- package/dist/counter-badge-recipe.js +5 -0
- package/dist/counter-badge-recipe.js.map +1 -1
- package/dist/dataviz.d.ts +76 -0
- package/dist/dataviz.d.ts.map +1 -0
- package/dist/dataviz.js +58 -0
- package/dist/dataviz.js.map +1 -0
- package/dist/date-range.d.ts +56 -0
- package/dist/date-range.d.ts.map +1 -0
- package/dist/date-range.js +79 -0
- package/dist/date-range.js.map +1 -0
- package/dist/design-system-provider.d.ts +24 -0
- package/dist/design-system-provider.d.ts.map +1 -1
- package/dist/design-system-provider.js +21 -0
- package/dist/design-system-provider.js.map +1 -1
- package/dist/floating-action-button.d.ts +1 -1
- package/dist/floating-action-button.d.ts.map +1 -1
- package/dist/floating-action-button.js +3 -1
- package/dist/floating-action-button.js.map +1 -1
- package/dist/formatters.d.ts +37 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +69 -0
- package/dist/formatters.js.map +1 -0
- package/dist/heading.d.ts +82 -0
- package/dist/heading.d.ts.map +1 -0
- package/dist/heading.js +48 -0
- package/dist/heading.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/menubar.d.ts +133 -0
- package/dist/menubar.d.ts.map +1 -0
- package/dist/menubar.js +80 -0
- package/dist/menubar.js.map +1 -0
- package/dist/native-platform.d.ts +88 -0
- package/dist/native-platform.d.ts.map +1 -0
- package/dist/native-platform.js +70 -0
- package/dist/native-platform.js.map +1 -0
- package/dist/popover.d.ts +19 -4
- package/dist/popover.d.ts.map +1 -1
- package/dist/popover.js +3 -0
- package/dist/popover.js.map +1 -1
- package/dist/progress-recipe.d.ts +14 -0
- package/dist/progress-recipe.d.ts.map +1 -1
- package/dist/progress-recipe.js +14 -1
- package/dist/progress-recipe.js.map +1 -1
- package/dist/provider-button.d.ts +140 -0
- package/dist/provider-button.d.ts.map +1 -0
- package/dist/provider-button.js +83 -0
- package/dist/provider-button.js.map +1 -0
- package/dist/recipes.d.ts +16 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +15 -0
- package/dist/recipes.js.map +1 -1
- package/dist/sheet.d.ts +16 -0
- package/dist/sheet.d.ts.map +1 -1
- package/dist/sheet.js +33 -0
- package/dist/sheet.js.map +1 -1
- package/dist/sidebar.d.ts +168 -0
- package/dist/sidebar.d.ts.map +1 -0
- package/dist/sidebar.js +98 -0
- package/dist/sidebar.js.map +1 -0
- package/dist/skip-nav.d.ts +67 -0
- package/dist/skip-nav.d.ts.map +1 -0
- package/dist/skip-nav.js +47 -0
- package/dist/skip-nav.js.map +1 -0
- package/dist/tags-input.d.ts +157 -0
- package/dist/tags-input.d.ts.map +1 -0
- package/dist/tags-input.js +108 -0
- package/dist/tags-input.js.map +1 -0
- package/dist/text-formats.d.ts +85 -0
- package/dist/text-formats.d.ts.map +1 -0
- package/dist/text-formats.js +43 -0
- package/dist/text-formats.js.map +1 -0
- package/dist/toggle-group.d.ts +111 -0
- package/dist/toggle-group.d.ts.map +1 -0
- package/dist/toggle-group.js +78 -0
- package/dist/toggle-group.js.map +1 -0
- package/dist/top.d.ts +113 -0
- package/dist/top.d.ts.map +1 -0
- package/dist/top.js +72 -0
- package/dist/top.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/agreement.md +33 -0
- package/docs/anchor.md +59 -59
- package/docs/ant-design-coverage.md +5 -3
- package/docs/asset.md +26 -0
- package/docs/auth-screen.md +65 -0
- package/docs/bottom-info.md +15 -0
- package/docs/breadcrumb.md +15 -15
- package/docs/button-label.md +20 -0
- package/docs/calendar.md +82 -154
- package/docs/carousel.md +27 -2
- package/docs/cascader.md +17 -0
- package/docs/chart.md +33 -0
- package/docs/clipboard.md +13 -0
- package/docs/collapsible.md +19 -0
- package/docs/command-palette.md +19 -0
- package/docs/confirm-popover.md +11 -1
- package/docs/context-menu.md +23 -0
- package/docs/data-table.md +22 -0
- package/docs/date-range.md +31 -0
- package/docs/density.md +52 -0
- package/docs/expansion-roadmap.md +16 -3
- package/docs/floating-action-button.md +35 -3
- package/docs/formatters.md +18 -0
- package/docs/generated/component-maturity.md +40 -23
- package/docs/generated/renderer-evidence.json +3029 -652
- package/docs/generated/renderer-evidence.md +55 -6
- package/docs/generated/showcase-manifest.json +1156 -81
- package/docs/heading.md +25 -0
- package/docs/identity.md +5 -0
- package/docs/layout.md +1 -1
- package/docs/library-gap-analysis.md +29 -9
- package/docs/list-row.md +14 -0
- package/docs/mentions.md +18 -0
- package/docs/menubar.md +22 -0
- package/docs/native-platform.md +36 -0
- package/docs/overlay-stack.md +24 -0
- package/docs/pagination.md +16 -8
- package/docs/popover.md +42 -13
- package/docs/product-audit-2026-09-15.md +88 -0
- package/docs/progress.md +16 -0
- package/docs/provider-button.md +33 -0
- package/docs/rating.md +19 -0
- package/docs/react-native-completion.md +347 -0
- package/docs/screen-chrome.md +52 -0
- package/docs/side-panel.md +23 -2
- package/docs/sidebar.md +30 -0
- package/docs/skip-nav.md +20 -0
- package/docs/splitter.md +22 -2
- package/docs/stable-promotion.md +51 -0
- package/docs/tags-input.md +25 -0
- package/docs/text-formats.md +15 -0
- package/docs/theming.md +84 -0
- package/docs/time-picker.md +22 -1
- package/docs/toast.md +18 -0
- package/docs/toggle-group.md +21 -0
- package/docs/top.md +32 -0
- package/docs/tour.md +23 -2
- package/docs/transfer-list.md +20 -0
- package/docs/tree-select.md +18 -0
- package/docs/tree.md +21 -0
- package/package.json +126 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset.js","sourceRoot":"","sources":["../src/asset.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport { radius, spacing } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * 아이콘·이미지·Lottie·비디오를 **같은 액자**에 넣는 자리.\n *\n * Icon·Image·Avatar가 이미 있는데 왜 또 두는가: 세 컴포넌트는 각자 다른 모양 규칙을 갖고\n * 있고(아이콘은 정사각, Avatar는 원, Image는 비율), 화면에서 이들이 **같은 줄에 섞여**\n * 나올 때 크기와 모서리가 어긋난다. Diairy의 `LottieArt`·`AnimalArt`가 그 어긋남을\n * 제품 안에서 다시 맞추고 있었다. Asset은 그 액자 규칙만 갖고, **무엇을 그릴지는 슬롯으로\n * 받는다** — Lottie와 비디오 재생기를 이 패키지가 의존하지 않기 위해서다.\n *\n * 그래서 이 계약에 재생 상태(play/pause/seek)가 없다. 재생기는 제품 것이고, 여기서\n * 정하는 것은 액자·크기·겹침·보조 표식뿐이다.\n */\nexport type AssetMediaKind = \"icon\" | \"image\" | \"lottie\" | \"video\";\nexport type AssetShape = \"square\" | \"rounded\" | \"circle\";\nexport type AssetSize = \"small\" | \"medium\" | \"large\" | \"xlarge\";\n\nexport type AssetDescriptor = Readonly<{\n kind: AssetMediaKind;\n size?: AssetSize;\n shape?: AssetShape;\n /**\n * 장식이면 `true`. 장식이 아닌데 이름이 없으면 거절한다 — 움직이는 그림일수록\n * \"무엇을 뜻하는지\"가 화면에 없으면 보조기기에서 사라진다.\n */\n decorative?: boolean;\n accessibilityLabel?: string;\n}>;\n\nexport function validateAssetDescriptor(descriptor: AssetDescriptor): void {\n if (!assetRecipe.kinds.includes(descriptor.kind)) {\n throw new TypeError(`Unsupported Asset kind: ${String(descriptor.kind)}`);\n }\n const decorative = descriptor.decorative ?? false;\n const label = descriptor.accessibilityLabel?.trim() ?? \"\";\n if (!decorative && label.length === 0) {\n throw new TypeError(\"A meaningful Asset must provide accessibilityLabel\");\n }\n if (decorative && label.length > 0) {\n // 둘 다 주면 어느 쪽이 참인지 화면이 모른다. 조용히 무시하면 이름을 지운 줄 모른다.\n throw new TypeError(\"A decorative Asset must not provide accessibilityLabel\");\n }\n}\n\n/**\n * 움직이는 자산은 `reducedMotion`에서 **첫 프레임(또는 포스터)로 정지**한다. 숨기지\n * 않는 이유: 그 자리에 그림이 있다는 사실 자체가 화면의 뜻인 경우가 많다(에어리의 동물).\n * 정지 화면을 제품이 주지 못하면 재생을 멈추기만 한다.\n */\nexport function shouldAnimateAsset(kind: AssetMediaKind, reducedMotion: boolean): boolean {\n return (kind === \"lottie\" || kind === \"video\") && !reducedMotion;\n}\n\nexport const assetRecipe = {\n slots: [\"root\", \"frame\", \"media\", \"accessory\", \"group\"] as const,\n kinds: [\"icon\", \"image\", \"lottie\", \"video\"] as const,\n defaults: { size: \"medium\", shape: \"rounded\" },\n sizes: { small: 32, medium: 48, large: 72, xlarge: 120 },\n // 정사각은 모서리 0이다 — radius 토큰에 0이 없는 이유는 \"모서리 없음\"이 토큰이 아니라\n // 값의 부재이기 때문이다.\n shapes: { square: 0, rounded: radius.md, circle: radius.full },\n background: semanticColors.surface.sunken,\n border: semanticColors.border.default,\n /** 묶음으로 겹칠 때의 비율. Avatar와 같은 값을 쓴다 — 한 줄에 섞여 나오기 때문이다. */\n overlapRatio: 0.3,\n /** 보조 표식(재생 아이콘·상태 점)은 액자 **바깥 모서리**에 붙어 매체를 가리지 않는다. */\n accessory: { offset: spacing.xxs, gap: spacing.xxs },\n} as const;\n\nexport const assetBehavior = {\n /** 상태가 없다 — 액자는 값을 갖지 않고 슬롯만 받는다. */\n controlled: [],\n inputs: [\"kind\", \"size\", \"shape\", \"decorative\", \"accessibilityLabel\"],\n configuration: {\n kind: [\"icon\", \"image\", \"lottie\", \"video\"],\n shape: [\"square\", \"rounded\", \"circle\"],\n size: [\"small\", \"medium\", \"large\", \"xlarge\"],\n },\n stateAxes: { availability: [\"enabled\"] },\n web: { roles: [\"img\", \"presentation\"], keyboard: [], focus: \"none\" },\n native: { roles: [\"image\"], states: [], actions: [] },\n scenarios: [\n \"one-frame-rule-covers-icon-image-lottie-and-video-so-a-mixed-row-lines-up\",\n \"the-package-never-depends-on-a-player-the-media-arrives-as-a-slot\",\n \"a-meaningful-asset-without-a-name-is-rejected-instead-of-silently-decorative\",\n \"reduced-motion-freezes-the-frame-rather-than-removing-the-asset\",\n \"an-accessory-sits-on-the-frames-outer-corner-and-never-covers-the-media\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 로그인 화면의 골격이다. 제품이 화면을 다시 조립하지 않게 **배치만** 소유한다.
|
|
3
|
+
*
|
|
4
|
+
* 왜 골격인가: 계정이 있는 제품들이 같은 화면을 각자 만들었고, 같은 실수를 각자 했다.
|
|
5
|
+
* 번뚝은 로고·태그라인·설명 두 줄·동의 고지·정책 링크를 위에서부터 쌓아 제공자 버튼을
|
|
6
|
+
* 화면 밖으로 밀어냈고(2026-09-19 iPhone 17 Pro 실측), 그 배치를 따라한 다른 제품도 같은
|
|
7
|
+
* 순서로 쌓였다. 간격과 최대 폭은 제품마다 다를 이유가 없는데 제품마다 다시 정해졌다.
|
|
8
|
+
*
|
|
9
|
+
* 그래서 **두 영역**만 강제한다. 위 영역(`hero` + `main`)은 남는 세로 공간을 전부 차지하고
|
|
10
|
+
* 그 안에서 가운데 정렬한다. 아래 영역(`footer`)은 바닥에 붙는다. 세 영역 이상으로 나누지
|
|
11
|
+
* 않는 이유는, 셋이 되는 순간 어느 것이 주 행동인지가 배치로 드러나지 않아서다.
|
|
12
|
+
*
|
|
13
|
+
* **HJM이 소유하는 것**: 영역 구분, 간격, 최대 폭, 가운데 정렬, 내용이 길어질 때의 스크롤 전환.
|
|
14
|
+
* **제품이 소유하는 것**: 마크 자산, 모든 문구, 제공자 목록과 순서, 정책 링크의 목적지,
|
|
15
|
+
* 그리고 `main`에 무엇을 넣을지(제공자 버튼만, 또는 가입 재개·심사자 입력 같은 제품 덩어리).
|
|
16
|
+
*
|
|
17
|
+
* 이 계약은 화면을 **그리지 않는다.** `AuthProviderButton`·`BottomInfo`·`Agreement`처럼 이미
|
|
18
|
+
* 있는 컴포넌트를 제품이 슬롯에 넣는다. 화면 전체를 한 컴포넌트로 받으면 제품마다 다른
|
|
19
|
+
* 부분(다에리의 중단 거래 복구, 번뚝의 심사자 폼)을 전부 슬롯으로 되돌려 받아야 해서,
|
|
20
|
+
* 결국 이름만 붙은 Stack이 된다.
|
|
21
|
+
*/
|
|
22
|
+
export type AuthScreenDensity = "compact" | "regular";
|
|
23
|
+
export type AuthScreenDescriptor = Readonly<{
|
|
24
|
+
/**
|
|
25
|
+
* 세로 공간이 좁은 기기에서 간격을 줄인다. 제품이 화면 높이를 보고 고르며, HJM은
|
|
26
|
+
* 기기 종류를 추측하지 않는다(같은 폭이라도 키보드가 올라오면 좁아진다).
|
|
27
|
+
*/
|
|
28
|
+
density?: AuthScreenDensity;
|
|
29
|
+
/** 아래 영역(동의 고지·정책 링크)을 둘지. 계정 생성이 없는 화면은 없을 수 있다. */
|
|
30
|
+
hasFooter?: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
export type ResolvedAuthScreenDescriptor = Readonly<{
|
|
33
|
+
density: AuthScreenDensity;
|
|
34
|
+
hasFooter: boolean;
|
|
35
|
+
/** 위 영역 안쪽 간격(마크→제목→설명→주 행동). */
|
|
36
|
+
heroGap: number;
|
|
37
|
+
/** 위 영역과 주 행동 사이. */
|
|
38
|
+
mainGap: number;
|
|
39
|
+
/** 아래 영역을 띄우는 최소 간격. 화면이 길면 그 이상 벌어진다. */
|
|
40
|
+
footerGap: number;
|
|
41
|
+
paddingInline: number;
|
|
42
|
+
paddingBlock: number;
|
|
43
|
+
maxWidth: number;
|
|
44
|
+
}>;
|
|
45
|
+
export declare const authScreenDefaults: {
|
|
46
|
+
readonly density: "regular";
|
|
47
|
+
readonly hasFooter: true;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* 최대 폭 26rem(416)은 `Container`의 reading(720)보다 좁다. 제공자 버튼이 노트북 폭만큼
|
|
51
|
+
* 길어지면 누를 곳이 아니라 띠로 보이기 때문이고, 이 화면의 내용은 버튼 한 줄기뿐이라
|
|
52
|
+
* 읽기 폭을 쓸 이유가 없다.
|
|
53
|
+
*/
|
|
54
|
+
export declare const authScreenRecipe: {
|
|
55
|
+
readonly slots: readonly ["root", "hero", "mark", "title", "description", "main", "footer"];
|
|
56
|
+
readonly defaults: {
|
|
57
|
+
readonly density: "regular";
|
|
58
|
+
readonly hasFooter: true;
|
|
59
|
+
};
|
|
60
|
+
readonly maxWidth: 416;
|
|
61
|
+
/** 마크는 아이콘(48)보다 크고 일러스트(120+)보다 작다 — 제품을 알아볼 최소 크기다. */
|
|
62
|
+
readonly markSize: 72;
|
|
63
|
+
readonly markRadius: 16;
|
|
64
|
+
readonly title: {
|
|
65
|
+
readonly fontSize: 20;
|
|
66
|
+
readonly lineHeight: 28;
|
|
67
|
+
readonly fontWeight: "800";
|
|
68
|
+
};
|
|
69
|
+
readonly description: {
|
|
70
|
+
readonly fontSize: 14;
|
|
71
|
+
readonly lineHeight: 20;
|
|
72
|
+
readonly fontWeight: "400";
|
|
73
|
+
};
|
|
74
|
+
/** 정책 링크는 글자 높이만큼만 누를 수 있으면 44pt 기준에 못 미친다. */
|
|
75
|
+
readonly footerMinTouchTarget: 44;
|
|
76
|
+
readonly footerText: {
|
|
77
|
+
readonly fontSize: 11;
|
|
78
|
+
readonly lineHeight: 16;
|
|
79
|
+
readonly fontWeight: "400";
|
|
80
|
+
};
|
|
81
|
+
/** 제공자 버튼 높이는 Provider 계약이 소유한다 — 여기서 다시 정하지 않는다. */
|
|
82
|
+
readonly providerMinHeight: 44;
|
|
83
|
+
readonly densities: {
|
|
84
|
+
readonly regular: {
|
|
85
|
+
readonly heroGap: 16;
|
|
86
|
+
readonly mainGap: 24;
|
|
87
|
+
readonly footerGap: 24;
|
|
88
|
+
readonly paddingInline: 20;
|
|
89
|
+
readonly paddingBlock: 40;
|
|
90
|
+
};
|
|
91
|
+
readonly compact: {
|
|
92
|
+
readonly heroGap: 8;
|
|
93
|
+
readonly mainGap: 16;
|
|
94
|
+
readonly footerGap: 16;
|
|
95
|
+
readonly paddingInline: 16;
|
|
96
|
+
readonly paddingBlock: 20;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
export declare function validateAuthScreenDescriptor(descriptor: AuthScreenDescriptor): void;
|
|
101
|
+
export declare function resolveAuthScreenDescriptor(descriptor?: AuthScreenDescriptor): ResolvedAuthScreenDescriptor;
|
|
102
|
+
export declare const authScreenBehavior: {
|
|
103
|
+
readonly controlled: readonly [];
|
|
104
|
+
readonly inputs: readonly ["density", "hasFooter"];
|
|
105
|
+
readonly events: readonly [];
|
|
106
|
+
readonly stateAxes: {};
|
|
107
|
+
readonly web: {
|
|
108
|
+
readonly roles: readonly ["main"];
|
|
109
|
+
readonly keyboard: readonly [];
|
|
110
|
+
readonly focus: "none";
|
|
111
|
+
};
|
|
112
|
+
readonly native: {
|
|
113
|
+
readonly roles: readonly [];
|
|
114
|
+
readonly states: readonly [];
|
|
115
|
+
readonly actions: readonly [];
|
|
116
|
+
};
|
|
117
|
+
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"];
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=auth-screen.d.ts.map
|
|
@@ -0,0 +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;CAClB,CAAC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;CAGoB,CAAC;AAEpD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;IAI3B,yDAAyD;;;;;;;;;;;;;IAKzD,+CAA+C;;;;;;;IAG/C,qDAAqD;;;;;;;;;;;;;;;;;;CAkB7C,CAAC;AAIX,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,oBAAoB,GAAG,IAAI,CAUnF;AAED,wBAAgB,2BAA2B,CACzC,UAAU,GAAE,oBAAyB,GACpC,4BAA4B,CAc9B;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;CAgBM,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { authProviderButtonRecipe } from "./provider-button.js";
|
|
2
|
+
import { control, layout, radius, spacing, typography } from "./foundations.js";
|
|
3
|
+
export const authScreenDefaults = {
|
|
4
|
+
density: "regular",
|
|
5
|
+
hasFooter: true,
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* 최대 폭 26rem(416)은 `Container`의 reading(720)보다 좁다. 제공자 버튼이 노트북 폭만큼
|
|
9
|
+
* 길어지면 누를 곳이 아니라 띠로 보이기 때문이고, 이 화면의 내용은 버튼 한 줄기뿐이라
|
|
10
|
+
* 읽기 폭을 쓸 이유가 없다.
|
|
11
|
+
*/
|
|
12
|
+
export const authScreenRecipe = {
|
|
13
|
+
slots: ["root", "hero", "mark", "title", "description", "main", "footer"],
|
|
14
|
+
defaults: authScreenDefaults,
|
|
15
|
+
maxWidth: 416,
|
|
16
|
+
/** 마크는 아이콘(48)보다 크고 일러스트(120+)보다 작다 — 제품을 알아볼 최소 크기다. */
|
|
17
|
+
markSize: 72,
|
|
18
|
+
markRadius: radius.lg,
|
|
19
|
+
title: typography.titleLarge,
|
|
20
|
+
description: typography.body,
|
|
21
|
+
/** 정책 링크는 글자 높이만큼만 누를 수 있으면 44pt 기준에 못 미친다. */
|
|
22
|
+
footerMinTouchTarget: control.minTouchTarget,
|
|
23
|
+
footerText: typography.caption,
|
|
24
|
+
/** 제공자 버튼 높이는 Provider 계약이 소유한다 — 여기서 다시 정하지 않는다. */
|
|
25
|
+
providerMinHeight: authProviderButtonRecipe.minHeight,
|
|
26
|
+
densities: {
|
|
27
|
+
regular: {
|
|
28
|
+
heroGap: spacing.md,
|
|
29
|
+
mainGap: spacing.xl,
|
|
30
|
+
footerGap: spacing.xl,
|
|
31
|
+
paddingInline: layout.pagePadding.regular,
|
|
32
|
+
paddingBlock: spacing.xxxl,
|
|
33
|
+
},
|
|
34
|
+
compact: {
|
|
35
|
+
heroGap: spacing.xs,
|
|
36
|
+
mainGap: spacing.md,
|
|
37
|
+
footerGap: spacing.md,
|
|
38
|
+
paddingInline: layout.pagePadding.compact,
|
|
39
|
+
paddingBlock: spacing.lg,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const densities = new Set(["compact", "regular"]);
|
|
44
|
+
export function validateAuthScreenDescriptor(descriptor) {
|
|
45
|
+
if (descriptor === null || typeof descriptor !== "object") {
|
|
46
|
+
throw new TypeError("AuthScreen descriptor must be an object");
|
|
47
|
+
}
|
|
48
|
+
if (descriptor.density !== undefined && !densities.has(descriptor.density)) {
|
|
49
|
+
throw new TypeError(`Unsupported AuthScreen density: ${String(descriptor.density)}`);
|
|
50
|
+
}
|
|
51
|
+
if (descriptor.hasFooter !== undefined && typeof descriptor.hasFooter !== "boolean") {
|
|
52
|
+
throw new TypeError("AuthScreen hasFooter must be a boolean");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function resolveAuthScreenDescriptor(descriptor = {}) {
|
|
56
|
+
validateAuthScreenDescriptor(descriptor);
|
|
57
|
+
const density = descriptor.density ?? authScreenDefaults.density;
|
|
58
|
+
const scale = authScreenRecipe.densities[density];
|
|
59
|
+
return {
|
|
60
|
+
density,
|
|
61
|
+
hasFooter: descriptor.hasFooter ?? authScreenDefaults.hasFooter,
|
|
62
|
+
heroGap: scale.heroGap,
|
|
63
|
+
mainGap: scale.mainGap,
|
|
64
|
+
footerGap: scale.footerGap,
|
|
65
|
+
paddingInline: scale.paddingInline,
|
|
66
|
+
paddingBlock: scale.paddingBlock,
|
|
67
|
+
maxWidth: authScreenRecipe.maxWidth,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export const authScreenBehavior = {
|
|
71
|
+
controlled: [],
|
|
72
|
+
inputs: ["density", "hasFooter"],
|
|
73
|
+
events: [],
|
|
74
|
+
stateAxes: {},
|
|
75
|
+
web: { roles: ["main"], keyboard: [], focus: "none" },
|
|
76
|
+
native: { roles: [], states: [], actions: [] },
|
|
77
|
+
scenarios: [
|
|
78
|
+
"the-hero-and-the-main-action-stay-one-vertically-centred-block",
|
|
79
|
+
"the-footer-sits-at-the-bottom-and-never-overlaps-the-main-action",
|
|
80
|
+
"content-taller-than-the-viewport-scrolls-instead-of-pushing-the-footer-off-screen",
|
|
81
|
+
"the-product-owns-every-string-and-the-mark-asset-while-the-layout-stays-here",
|
|
82
|
+
"provider-button-height-comes-from-the-provider-contract-not-from-this-one",
|
|
83
|
+
"compact-density-shrinks-gaps-without-changing-the-two-region-structure",
|
|
84
|
+
"policy-links-in-the-footer-keep-the-minimum-touch-target",
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=auth-screen.js.map
|
|
@@ -0,0 +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 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"]}
|
package/dist/base-recipes.d.ts
CHANGED
|
@@ -98,7 +98,26 @@ export declare const buttonRecipe: {
|
|
|
98
98
|
readonly disabled: 0.5;
|
|
99
99
|
readonly pressed: 0.86;
|
|
100
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* 긴 라벨의 처리 규칙. 지금까지 이 판단이 없어서 Web은 무한히 줄바꿈하고 RN은 한 줄로
|
|
103
|
+
* 잘렸다 — 같은 번역문이 두 표면에서 다르게 보였다.
|
|
104
|
+
*
|
|
105
|
+
* 자르지 않고 **두 줄까지 접는다**. 잘린 행동 라벨("삭제하..."))은 무엇을 하는 버튼인지
|
|
106
|
+
* 잃는다. 대신 무제한 줄바꿈도 막는다 — 하단 CTA 행처럼 버튼이 남의 레이아웃을 밀어내는
|
|
107
|
+
* 자리가 있고, 두 줄을 넘는 라벨은 레이아웃이 아니라 카피의 문제다.
|
|
108
|
+
*
|
|
109
|
+
* 큰 글자 설정에서는 상한을 **푼다**. 사용자가 키운 글자에서 두 줄로 자르면 내용이
|
|
110
|
+
* 사라지고, 그건 WCAG 1.4.4를 어긴다. 제품 카피가 아니라 사용자 설정이 원인이기 때문에
|
|
111
|
+
* 같은 규칙을 적용할 수 없다.
|
|
112
|
+
*/
|
|
113
|
+
readonly label: {
|
|
114
|
+
readonly overflow: "wrap";
|
|
115
|
+
readonly maxLines: 2;
|
|
116
|
+
readonly liftCapOnLargeText: true;
|
|
117
|
+
};
|
|
101
118
|
};
|
|
119
|
+
/** Resolves the label's line cap for one resolved environment. `null` means uncapped. */
|
|
120
|
+
export declare function resolveButtonLabelLines(largeText: boolean): number | null;
|
|
102
121
|
export declare const surfaceRecipe: {
|
|
103
122
|
readonly default: {
|
|
104
123
|
readonly background: "surface";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-recipes.d.ts","sourceRoot":"","sources":["../src/base-recipes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAc,MAAM,EAAE,OAAO,EAAc,MAAM,kBAAkB,CAAC;AAGpF,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC/E,uEAAuE;AACvE,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;AAC7C;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC/C,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,OAAO,CAAC,YAAY,CAAC;AAC3D,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAChF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,OAAO,OAAO,CAAC;AAC3D,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,MAAM,CAAC;AAChD,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,CAAC;AAC/C,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAErD,4EAA4E;AAC5E,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAcvB;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"base-recipes.d.ts","sourceRoot":"","sources":["../src/base-recipes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAc,MAAM,EAAE,OAAO,EAAc,MAAM,kBAAkB,CAAC;AAGpF,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC/E,uEAAuE;AACvE,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;AAC7C;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC/C,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,OAAO,CAAC,YAAY,CAAC;AAC3D,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAChF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,OAAO,OAAO,CAAC;AAC3D,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,MAAM,CAAC;AAChD,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,CAAC;AAC/C,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAErD,4EAA4E;AAC5E,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAcvB;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2BH;;;;;;;;;;;OAWG;;;;;;CAmCJ,CAAC;AAEF,yFAAyF;AACzF,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEzE;AAED,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDzB,CAAC;AAEF,6CAA6C;AAC7C,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE5C,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4BtB;;;;OAIG;uCAC+B,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;CAqDhD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe;;;;;CAU3B,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;CAM3B,CAAC"}
|
package/dist/base-recipes.js
CHANGED
|
@@ -45,7 +45,24 @@ export const buttonRecipe = {
|
|
|
45
45
|
shapes: { rounded: "md", pill: "full" },
|
|
46
46
|
aligns: { center: "center", leading: "flex-start" },
|
|
47
47
|
opacity: { disabled: 0.5, pressed: 0.86 },
|
|
48
|
+
/**
|
|
49
|
+
* 긴 라벨의 처리 규칙. 지금까지 이 판단이 없어서 Web은 무한히 줄바꿈하고 RN은 한 줄로
|
|
50
|
+
* 잘렸다 — 같은 번역문이 두 표면에서 다르게 보였다.
|
|
51
|
+
*
|
|
52
|
+
* 자르지 않고 **두 줄까지 접는다**. 잘린 행동 라벨("삭제하..."))은 무엇을 하는 버튼인지
|
|
53
|
+
* 잃는다. 대신 무제한 줄바꿈도 막는다 — 하단 CTA 행처럼 버튼이 남의 레이아웃을 밀어내는
|
|
54
|
+
* 자리가 있고, 두 줄을 넘는 라벨은 레이아웃이 아니라 카피의 문제다.
|
|
55
|
+
*
|
|
56
|
+
* 큰 글자 설정에서는 상한을 **푼다**. 사용자가 키운 글자에서 두 줄로 자르면 내용이
|
|
57
|
+
* 사라지고, 그건 WCAG 1.4.4를 어긴다. 제품 카피가 아니라 사용자 설정이 원인이기 때문에
|
|
58
|
+
* 같은 규칙을 적용할 수 없다.
|
|
59
|
+
*/
|
|
60
|
+
label: { overflow: "wrap", maxLines: 2, liftCapOnLargeText: true },
|
|
48
61
|
};
|
|
62
|
+
/** Resolves the label's line cap for one resolved environment. `null` means uncapped. */
|
|
63
|
+
export function resolveButtonLabelLines(largeText) {
|
|
64
|
+
return largeText && buttonRecipe.label.liftCapOnLargeText ? null : buttonRecipe.label.maxLines;
|
|
65
|
+
}
|
|
49
66
|
export const surfaceRecipe = {
|
|
50
67
|
default: {
|
|
51
68
|
background: "surface",
|
package/dist/base-recipes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-recipes.js","sourceRoot":"","sources":["../src/base-recipes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAkBpF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,KAAK,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,CAAU;IACnE,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAW;IACzF,KAAK,EAAE;QACL,OAAO,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;QAC/F,0EAA0E;QAC1E,uEAAuE;QACvE,SAAS,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,iBAAiB,EAAE,IAAI,EAAE;QAC1G,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;QACxF,MAAM,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;QAChG,2EAA2E;QAC3E,0DAA0D;QAC1D,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,EAAE;KACxF;IACD;;;;OAIG;IACH,MAAM,EAAE;QACN,QAAQ,EAAE,EAAE,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,EAAE;KAC3F;IACD,KAAK,EAAE;QACL,KAAK,EAAE;YACL,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK;YAClC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK;YACpC,iBAAiB,EAAE,OAAO,CAAC,EAAE;YAC7B,WAAW,EAAE,OAAO;SACrB;QACD,MAAM,EAAE;YACN,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,MAAM;YACnC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,MAAM;YACrC,iBAAiB,EAAE,OAAO,CAAC,EAAE;YAC7B,WAAW,EAAE,MAAM;SACpB;QACD,KAAK,EAAE;YACL,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK;YAClC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK;YACpC,iBAAiB,EAAE,OAAO,CAAC,EAAE;YAC7B,WAAW,EAAE,WAAW;SACzB;KACF;IACD,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE;IACvC,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;IACnD,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;CAiC1C,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,OAAO,EAAE;QACP,UAAU,EAAE,SAAS;QACrB,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,KAAK;QACf,YAAY,EAAE,KAAK;QACnB,YAAY,EAAE,IAAI;KACnB;IACD,MAAM,EAAE;QACN,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,KAAK;QACnB,yEAAyE;QACzE,wEAAwE;QACxE,iEAAiE;QACjE,YAAY,EAAE,KAAK;KACpB;IACD,4EAA4E;IAC5E,uEAAuE;IACvE,MAAM,EAAE;QACN,UAAU,EAAE,YAAY;QACxB,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,KAAK;QACf,YAAY,EAAE,KAAK;QACnB,YAAY,EAAE,IAAI;KACnB;IACD,MAAM,EAAE;QACN,UAAU,EAAE,eAAe;QAC3B,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,GAAG;QAChB,QAAQ,EAAE,KAAK;QACf,YAAY,EAAE,KAAK;QACnB,YAAY,EAAE,IAAI;KACnB;IACD,MAAM,EAAE;QACN,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,KAAK;QACf,YAAY,EAAE,IAAI;QAClB,YAAY,EAAE,IAAI;KACnB;CAWF,CAAC;AAKF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,KAAK,EAAE;QACL,MAAM;QACN,OAAO;QACP,SAAS;QACT,SAAS;QACT,OAAO;QACP,UAAU;QACV,MAAM;QACN,OAAO;KACC;IACV,QAAQ,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAW;IAC1E,QAAQ,EAAE;QACR,OAAO,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE;QAClC,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;KAC5B;IACD,MAAM,EAAE;QACN,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,MAAM;KACb;IACD,MAAM,EAAE;QACN,IAAI,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE;QAC7B,OAAO,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE;QACnC,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE;KAC9B;IACD,SAAS,EAAE,OAAO,CAAC,cAAc;IACjC,kBAAkB,EAAE,EAAE;IACtB;;;;OAIG;IACH,wBAAwB,EAAE,IAAqB;IAC/C,WAAW,EAAE,CAAC;IACd,cAAc,EAAE,CAAC;IACjB,eAAe,EAAE,CAAC;IAClB,iBAAiB,EAAE,OAAO,CAAC,EAAE;IAC7B,eAAe,EAAE,OAAO,CAAC,EAAE;IAC3B,WAAW,EAAE,MAAM;IACnB,KAAK,EAAE;QACL,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,MAAM;QACnB,UAAU,EAAE,UAAU,CAAC,QAAQ;QAC/B,GAAG,EAAE,OAAO,CAAC,EAAE;KAChB;IACD,OAAO,EAAE;QACP,SAAS,EAAE,WAAW;QACtB,UAAU,EAAE,QAAQ;QACpB,WAAW,EAAE,OAAO;QACpB,GAAG,EAAE,CAAC;KACP;IACD,WAAW,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE;IACnC,eAAe,EAAE,GAAG;CAiCrB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,MAAM;IACf,MAAM,EAAE,IAAI;IACZ,QAAQ,EAAE,KAAK;CAMhB,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE;IACjC,KAAK,EAAE,MAAM;CAId,CAAC","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: \"surfaceAlt\", 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: { background: \"surfaceAccent\", content: \"contentBrand\", border: \"contentBrand\" },\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} 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;\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};\n\nexport const surfaceRecipe = {\n default: {\n background: \"surface\",\n border: \"border\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: false,\n clipsContent: true,\n },\n raised: {\n background: \"bg\",\n border: \"border\",\n borderAlpha: 1,\n elevated: true,\n borderAlways: false,\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 // `semanticColors.surface.sunken` already named this role; without a paired\n // tone a consumer had to paint `surfaceAlt` in its own product styles.\n sunken: {\n background: \"surfaceAlt\",\n border: \"border\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: false,\n clipsContent: true,\n },\n accent: {\n background: \"surfaceAccent\",\n border: \"primary\",\n borderAlpha: 0.3,\n elevated: false,\n borderAlways: false,\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 surface: { background: \"surface\" },\n inset: { background: \"bg\" },\n },\n shapes: {\n medium: \"md\",\n large: \"lg\",\n full: \"full\",\n },\n states: {\n idle: { border: \"textMuted\" },\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} 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};\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: \"surfaceAlt\", 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: { background: \"surfaceAccent\", content: \"contentBrand\", border: \"contentBrand\" },\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;\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/** 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: \"surface\",\n border: \"border\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: false,\n clipsContent: true,\n },\n raised: {\n background: \"bg\",\n border: \"border\",\n borderAlpha: 1,\n elevated: true,\n borderAlways: false,\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 // `semanticColors.surface.sunken` already named this role; without a paired\n // tone a consumer had to paint `surfaceAlt` in its own product styles.\n sunken: {\n background: \"surfaceAlt\",\n border: \"border\",\n borderAlpha: 1,\n elevated: false,\n borderAlways: false,\n clipsContent: true,\n },\n accent: {\n background: \"surfaceAccent\",\n border: \"primary\",\n borderAlpha: 0.3,\n elevated: false,\n borderAlways: false,\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 surface: { background: \"surface\" },\n inset: { background: \"bg\" },\n },\n shapes: {\n medium: \"md\",\n large: \"lg\",\n full: \"full\",\n },\n states: {\n idle: { border: \"textMuted\" },\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} 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};\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"]}
|