@hjmds/design-contracts 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog.d.ts +27 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/component-recipes.d.ts +12 -0
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +2 -0
- package/dist/component-recipes.js.map +1 -1
- package/dist/image.d.ts +36 -0
- package/dist/image.d.ts.map +1 -1
- package/dist/image.js +44 -0
- package/dist/image.js.map +1 -1
- package/dist/layout.d.ts +10 -0
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +11 -1
- package/dist/layout.js.map +1 -1
- package/dist/tag.d.ts +20 -7
- package/dist/tag.d.ts.map +1 -1
- package/dist/tag.js +20 -9
- package/dist/tag.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/generated/component-maturity.md +1 -1
- package/docs/generated/renderer-evidence.json +3 -3
- package/docs/generated/renderer-evidence.md +1 -1
- package/docs/generated/showcase-manifest.json +3 -2
- package/docs/layout.md +3 -1
- package/docs/tag.md +14 -5
- package/docs/usage/components/avatar.md +9 -0
- package/docs/usage/components/chart.md +1 -0
- package/docs/usage/components/comment-thread-row.md +4 -1
- package/docs/usage/components/image.md +7 -0
- package/docs/usage/components/layout.md +4 -3
- package/docs/usage/components/profile-summary.md +25 -0
- package/docs/usage/components/radio-group.md +10 -0
- package/docs/usage/components/sheet.md +1 -1
- package/docs/usage/components/sidebar.md +2 -0
- package/docs/usage/components/tag.md +8 -5
- package/package.json +1 -1
package/dist/image.d.ts
CHANGED
|
@@ -108,5 +108,41 @@ export declare const imageViewerTriggerRecipe: {
|
|
|
108
108
|
readonly disabledOpacity: 0.5;
|
|
109
109
|
};
|
|
110
110
|
export declare function validateImageViewerTriggerLabel(label: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* Circular profile-photo crop (2026-10-29 user rule: profile photos are framed in a circle like Instagram before
|
|
113
|
+
* upload). Web and Native draw the same square viewport with a circular mask; the product crops the returned
|
|
114
|
+
* source-pixel square and uploads it. The square keeps the circle's bounding box, so a cover-fitted avatar shows
|
|
115
|
+
* exactly the framed circle.
|
|
116
|
+
*/
|
|
117
|
+
export declare const circularPhotoCropRecipe: {
|
|
118
|
+
/** Largest zoom over the cover scale; beyond this a small photo turns into blur. */
|
|
119
|
+
readonly maxZoom: 4;
|
|
120
|
+
/** Mask outside the circle (on the black editing surface). */
|
|
121
|
+
readonly maskOpacity: 0.6;
|
|
122
|
+
/** Hairline around the circle so its edge reads on dark and light photos. */
|
|
123
|
+
readonly ringWidth: 1;
|
|
124
|
+
/** Horizontal space kept around the viewport, in px/pt. */
|
|
125
|
+
readonly gutter: 24;
|
|
126
|
+
};
|
|
127
|
+
export type CircularPhotoCropState = Readonly<{
|
|
128
|
+
zoom: number;
|
|
129
|
+
x: number;
|
|
130
|
+
y: number;
|
|
131
|
+
}>;
|
|
132
|
+
export type CircularPhotoCropRect = Readonly<{
|
|
133
|
+
originX: number;
|
|
134
|
+
originY: number;
|
|
135
|
+
width: number;
|
|
136
|
+
height: number;
|
|
137
|
+
}>;
|
|
138
|
+
/** Display px per source px at zoom 1: the photo covers the viewport on its short side. */
|
|
139
|
+
export declare function circularPhotoCoverScale(sourceWidth: number, sourceHeight: number, viewport: number): number;
|
|
140
|
+
/**
|
|
141
|
+
* Keeps the photo covering the viewport: zoom within [1, maxZoom] and the offset (photo centre from viewport
|
|
142
|
+
* centre, display px) within the overhang, so the circle never shows empty space.
|
|
143
|
+
*/
|
|
144
|
+
export declare function clampCircularPhotoCrop(state: CircularPhotoCropState, sourceWidth: number, sourceHeight: number, viewport: number): CircularPhotoCropState;
|
|
145
|
+
/** Source-pixel square under the viewport, rounded inside the photo bounds. */
|
|
146
|
+
export declare function resolveCircularPhotoCrop(state: CircularPhotoCropState, sourceWidth: number, sourceHeight: number, viewport: number): CircularPhotoCropRect;
|
|
111
147
|
export {};
|
|
112
148
|
//# sourceMappingURL=image.d.ts.map
|
package/dist/image.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AACpD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtE,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE9D,sGAAsG;AACtG,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,QAAQ,EAAE,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EACrD,IAAI,EAAE,mBAAmB;;;;;;;;EAiB1B;AAED,eAAO,MAAM,aAAa;;CAEsB,CAAC;AAEjD,KAAK,eAAe,GAAG,QAAQ,CAAC;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,QAAQ,CAAC;CAChB,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG,eAAe,GACrD,QAAQ,CAAC;IAAE,UAAU,CAAC,EAAE,IAAI,CAAC;IAAC,kBAAkB,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC;AAE9D,MAAM,MAAM,0BAA0B,GAAG,eAAe,GACtD,QAAQ,CAAC;IAAE,UAAU,EAAE,KAAK,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE9D,MAAM,MAAM,eAAe,GACvB,yBAAyB,GACzB,0BAA0B,CAAC;AAE/B,MAAM,MAAM,uBAAuB,GAC/B,CAAC,QAAQ,CAAC,IAAI,CAAC,yBAAyB,EAAE,oBAAoB,CAAC,CAAC,GAC9D,QAAQ,CAAC;IAAE,kBAAkB,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC,GAC3C,QAAQ,CAAC,0BAA0B,CAAC,CAAC;AAgBzC,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CA6BzE;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,GAC1B,uBAAuB,CAgBzB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAI7E;AAED;;;;;GAKG;AACH,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,uBAAuB,GAClC,MAAM,GAAG,SAAS,CAEpB;AAKD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;CAIe,CAAC;AAE9C,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;CAYd,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB;;;;;CAK3B,CAAC;AAEX,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEnE"}
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"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"}
|
package/dist/image.js
CHANGED
|
@@ -133,4 +133,48 @@ export function validateImageViewerTriggerLabel(label) {
|
|
|
133
133
|
if (typeof label !== "string" || label.trim().length === 0)
|
|
134
134
|
throw new TypeError("ImageViewerTrigger needs a localized label");
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Circular profile-photo crop (2026-10-29 user rule: profile photos are framed in a circle like Instagram before
|
|
138
|
+
* upload). Web and Native draw the same square viewport with a circular mask; the product crops the returned
|
|
139
|
+
* source-pixel square and uploads it. The square keeps the circle's bounding box, so a cover-fitted avatar shows
|
|
140
|
+
* exactly the framed circle.
|
|
141
|
+
*/
|
|
142
|
+
export const circularPhotoCropRecipe = {
|
|
143
|
+
/** Largest zoom over the cover scale; beyond this a small photo turns into blur. */
|
|
144
|
+
maxZoom: 4,
|
|
145
|
+
/** Mask outside the circle (on the black editing surface). */
|
|
146
|
+
maskOpacity: 0.6,
|
|
147
|
+
/** Hairline around the circle so its edge reads on dark and light photos. */
|
|
148
|
+
ringWidth: 1,
|
|
149
|
+
/** Horizontal space kept around the viewport, in px/pt. */
|
|
150
|
+
gutter: 24,
|
|
151
|
+
};
|
|
152
|
+
/** Display px per source px at zoom 1: the photo covers the viewport on its short side. */
|
|
153
|
+
export function circularPhotoCoverScale(sourceWidth, sourceHeight, viewport) {
|
|
154
|
+
if (!(sourceWidth > 0 && sourceHeight > 0 && viewport > 0))
|
|
155
|
+
throw new RangeError("Crop needs positive source and viewport sizes");
|
|
156
|
+
return viewport / Math.min(sourceWidth, sourceHeight);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Keeps the photo covering the viewport: zoom within [1, maxZoom] and the offset (photo centre from viewport
|
|
160
|
+
* centre, display px) within the overhang, so the circle never shows empty space.
|
|
161
|
+
*/
|
|
162
|
+
export function clampCircularPhotoCrop(state, sourceWidth, sourceHeight, viewport) {
|
|
163
|
+
const zoom = Math.min(circularPhotoCropRecipe.maxZoom, Math.max(1, state.zoom));
|
|
164
|
+
const scale = circularPhotoCoverScale(sourceWidth, sourceHeight, viewport) * zoom;
|
|
165
|
+
const limitX = Math.max(0, (sourceWidth * scale - viewport) / 2);
|
|
166
|
+
const limitY = Math.max(0, (sourceHeight * scale - viewport) / 2);
|
|
167
|
+
return { zoom, x: Math.min(limitX, Math.max(-limitX, state.x)), y: Math.min(limitY, Math.max(-limitY, state.y)) };
|
|
168
|
+
}
|
|
169
|
+
/** Source-pixel square under the viewport, rounded inside the photo bounds. */
|
|
170
|
+
export function resolveCircularPhotoCrop(state, sourceWidth, sourceHeight, viewport) {
|
|
171
|
+
const clamped = clampCircularPhotoCrop(state, sourceWidth, sourceHeight, viewport);
|
|
172
|
+
const scale = circularPhotoCoverScale(sourceWidth, sourceHeight, viewport) * clamped.zoom;
|
|
173
|
+
const size = Math.min(sourceWidth, sourceHeight, Math.round(viewport / scale));
|
|
174
|
+
const centreX = sourceWidth / 2 - clamped.x / scale;
|
|
175
|
+
const centreY = sourceHeight / 2 - clamped.y / scale;
|
|
176
|
+
const originX = Math.min(sourceWidth - size, Math.max(0, Math.round(centreX - size / 2)));
|
|
177
|
+
const originY = Math.min(sourceHeight - size, Math.max(0, Math.round(centreY - size / 2)));
|
|
178
|
+
return { originX, originY, width: size, height: size };
|
|
179
|
+
}
|
|
136
180
|
//# sourceMappingURL=image.js.map
|
package/dist/image.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.js","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SemanticIconName } from \"./icon.js\";\nimport type { IconTone } from \"./component-recipes.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport { opacity } from \"./foundations.js\";\n\nexport type ImageFit = \"cover\" | \"contain\" | \"fill\";\nexport type ImageLoadStatus = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\nexport type ImageInspectionMode = \"fit\" | \"double\" | \"pixels\";\n\n/** Exact image inspection sizes use layout units, not device physical pixels (Utilverse ADR-0020). */\nexport function resolveImageInspectionGeometry(\n image: Readonly<{ width: number; height: number }>,\n viewport: Readonly<{ width: number; height: number }>,\n mode: ImageInspectionMode,\n) {\n if (![image.width, image.height, viewport.width, viewport.height].every(value => Number.isFinite(value) && value > 0)) {\n throw new RangeError(\"Image inspection needs positive finite image and viewport sizes\");\n }\n if (mode !== \"fit\" && mode !== \"double\" && mode !== \"pixels\") throw new TypeError(\"Unknown image inspection mode\");\n // Fit can enlarge a small result, as the existing product preview does. Clamping\n // it to 1 would make fit and pixels indistinguishable for those images.\n const scale = mode === \"pixels\" ? 1 : Math.min(viewport.width / image.width, viewport.height / image.height) * (mode === \"double\" ? 2 : 1);\n const width = image.width * scale, height = image.height * scale;\n if (![scale, width, height].every(value => Number.isFinite(value) && value > 0)) {\n throw new RangeError(\"Image inspection geometry exceeds finite layout range\");\n }\n return { scale, width, height, panBounds: {\n x: Math.max(0, width - viewport.width) / 2,\n y: Math.max(0, height - viewport.height) / 2,\n } };\n}\n\nexport const imageDefaults = {\n fit: \"cover\",\n} as const satisfies Readonly<{ fit: ImageFit }>;\n\ntype ImageAppearance = Readonly<{\n src: string;\n /** Intrinsic dimensions reserve layout space before the asset loads. */\n width: number;\n height: number;\n fit?: ImageFit;\n}>;\n\n/**\n * Same shape as Icon's decorative/informative split: a picture that only\n * repeats a caption or label already visible nearby stays decorative, and\n * only a self-sufficient picture requires localized alt copy.\n */\nexport type DecorativeImageDescriptor = ImageAppearance &\n Readonly<{ decorative?: true; accessibilityLabel?: never }>;\n\nexport type InformativeImageDescriptor = ImageAppearance &\n Readonly<{ decorative: false; accessibilityLabel: string }>;\n\nexport type ImageDescriptor =\n | DecorativeImageDescriptor\n | InformativeImageDescriptor;\n\nexport type ResolvedImageDescriptor =\n | (Required<Omit<DecorativeImageDescriptor, \"accessibilityLabel\">> &\n Readonly<{ accessibilityLabel?: never }>)\n | Required<InformativeImageDescriptor>;\n\nconst fits: readonly ImageFit[] = [\"cover\", \"contain\", \"fill\"];\n\nfunction assertCopy(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Image ${field} must not be empty`);\n }\n}\n\nfunction assertDimension(value: number, field: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError(`Image ${field} must be a positive finite number`);\n }\n}\n\nexport function validateImageDescriptor(descriptor: ImageDescriptor): void {\n assertCopy(descriptor.src, \"src\");\n assertDimension(descriptor.width, \"width\");\n assertDimension(descriptor.height, \"height\");\n if (descriptor.fit !== undefined && !fits.includes(descriptor.fit)) {\n throw new TypeError(`Unsupported Image fit: ${String(descriptor.fit)}`);\n }\n const runtimeDecorative = (\n descriptor as Readonly<{ decorative?: unknown }>\n ).decorative;\n const runtimeLabel = (\n descriptor as Readonly<{ accessibilityLabel?: unknown }>\n ).accessibilityLabel;\n if (\n runtimeDecorative !== undefined &&\n runtimeDecorative !== true &&\n runtimeDecorative !== false\n ) {\n throw new TypeError(\"Image decorative must be a boolean when provided\");\n }\n if (runtimeDecorative === false) {\n if (typeof runtimeLabel !== \"string\" || runtimeLabel.trim().length === 0) {\n throw new TypeError(\n \"Informative Image accessibilityLabel must not be empty\",\n );\n }\n } else if (runtimeLabel !== undefined) {\n throw new TypeError(\"Decorative Image must not provide accessibilityLabel\");\n }\n}\n\nexport function resolveImageDescriptor(\n descriptor: ImageDescriptor,\n): ResolvedImageDescriptor {\n validateImageDescriptor(descriptor);\n const common = {\n src: descriptor.src,\n width: descriptor.width,\n height: descriptor.height,\n fit: descriptor.fit ?? imageDefaults.fit,\n } as const;\n if (descriptor.decorative === false) {\n return {\n ...common,\n decorative: false,\n accessibilityLabel: descriptor.accessibilityLabel,\n } as ResolvedImageDescriptor;\n }\n return { ...common, decorative: true } as ResolvedImageDescriptor;\n}\n\n/**\n * Reserves layout space ahead of load so a slow asset never shifts\n * surrounding content — the same problem `width`/`height` solve for\n * `next/image`, translated without a bundler dependency.\n */\nexport function resolveImageAspectRatio(width: number, height: number): number {\n assertDimension(width, \"width\");\n assertDimension(height, \"height\");\n return width / height;\n}\n\n/**\n * The fallback node on `error` always carries the resolved\n * `accessibilityLabel`, never a generic \"broken image\" string. An\n * informative picture's meaning does not disappear because the asset\n * failed to load; only its visual form changes.\n */\nexport function resolveImageFallbackAccessibilityLabel(\n descriptor: ResolvedImageDescriptor,\n): string | undefined {\n return descriptor.decorative ? undefined : descriptor.accessibilityLabel;\n}\n\nconst errorIconName: SemanticIconName = \"error\";\nconst fallbackIconTone: IconTone = \"secondary\";\n\n/**\n * Web `object-fit` and RN `resizeMode` diverge only in the `fill` case;\n * `fills` is the platform-neutral name and `nativeResizeModes` is the one\n * translation renderers need, kept here instead of re-derived per product.\n */\nexport const nativeResizeModes = {\n cover: \"cover\",\n contain: \"contain\",\n fill: \"stretch\",\n} as const satisfies Record<ImageFit, string>;\n\nexport const imageRecipe = {\n slots: [\"root\", \"image\", \"placeholder\", \"fallbackIcon\"] as const,\n defaults: imageDefaults,\n fits,\n placeholder: {\n background: semanticColors.canvas,\n },\n fallback: {\n background: semanticColors.canvas,\n icon: { name: errorIconName, tone: fallbackIconTone },\n },\n radius: \"md\",\n} as const;\n\n/**\n * Press target that opens ImageViewer from product media (ImageViewerTrigger). 2026-10-23: the portfolio site\n * (`App.tsx` raw button) and Spint (`MarkPhoto`, `ComposeScreen` Pressable + Image) each wrapped an image to open the\n * viewer, with their own name, focus ring and cursor. The trigger owns the button semantics, the image-frame\n * radius for the focus ring, the zoom cursor and the pressed opacity; the media stays decorative inside it.\n */\nexport const imageViewerTriggerRecipe = {\n radius: imageRecipe.radius,\n cursor: \"zoom-in\",\n pressedOpacity: opacity.pressed,\n disabledOpacity: opacity.disabled,\n} as const;\n\nexport function validateImageViewerTriggerLabel(label: string): void {\n if (typeof label !== \"string\" || label.trim().length === 0) throw new TypeError(\"ImageViewerTrigger needs a localized label\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SemanticIconName } from \"./icon.js\";\nimport type { IconTone } from \"./component-recipes.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport { opacity } from \"./foundations.js\";\n\nexport type ImageFit = \"cover\" | \"contain\" | \"fill\";\nexport type ImageLoadStatus = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\nexport type ImageInspectionMode = \"fit\" | \"double\" | \"pixels\";\n\n/** Exact image inspection sizes use layout units, not device physical pixels (Utilverse ADR-0020). */\nexport function resolveImageInspectionGeometry(\n image: Readonly<{ width: number; height: number }>,\n viewport: Readonly<{ width: number; height: number }>,\n mode: ImageInspectionMode,\n) {\n if (![image.width, image.height, viewport.width, viewport.height].every(value => Number.isFinite(value) && value > 0)) {\n throw new RangeError(\"Image inspection needs positive finite image and viewport sizes\");\n }\n if (mode !== \"fit\" && mode !== \"double\" && mode !== \"pixels\") throw new TypeError(\"Unknown image inspection mode\");\n // Fit can enlarge a small result, as the existing product preview does. Clamping\n // it to 1 would make fit and pixels indistinguishable for those images.\n const scale = mode === \"pixels\" ? 1 : Math.min(viewport.width / image.width, viewport.height / image.height) * (mode === \"double\" ? 2 : 1);\n const width = image.width * scale, height = image.height * scale;\n if (![scale, width, height].every(value => Number.isFinite(value) && value > 0)) {\n throw new RangeError(\"Image inspection geometry exceeds finite layout range\");\n }\n return { scale, width, height, panBounds: {\n x: Math.max(0, width - viewport.width) / 2,\n y: Math.max(0, height - viewport.height) / 2,\n } };\n}\n\nexport const imageDefaults = {\n fit: \"cover\",\n} as const satisfies Readonly<{ fit: ImageFit }>;\n\ntype ImageAppearance = Readonly<{\n src: string;\n /** Intrinsic dimensions reserve layout space before the asset loads. */\n width: number;\n height: number;\n fit?: ImageFit;\n}>;\n\n/**\n * Same shape as Icon's decorative/informative split: a picture that only\n * repeats a caption or label already visible nearby stays decorative, and\n * only a self-sufficient picture requires localized alt copy.\n */\nexport type DecorativeImageDescriptor = ImageAppearance &\n Readonly<{ decorative?: true; accessibilityLabel?: never }>;\n\nexport type InformativeImageDescriptor = ImageAppearance &\n Readonly<{ decorative: false; accessibilityLabel: string }>;\n\nexport type ImageDescriptor =\n | DecorativeImageDescriptor\n | InformativeImageDescriptor;\n\nexport type ResolvedImageDescriptor =\n | (Required<Omit<DecorativeImageDescriptor, \"accessibilityLabel\">> &\n Readonly<{ accessibilityLabel?: never }>)\n | Required<InformativeImageDescriptor>;\n\nconst fits: readonly ImageFit[] = [\"cover\", \"contain\", \"fill\"];\n\nfunction assertCopy(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Image ${field} must not be empty`);\n }\n}\n\nfunction assertDimension(value: number, field: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError(`Image ${field} must be a positive finite number`);\n }\n}\n\nexport function validateImageDescriptor(descriptor: ImageDescriptor): void {\n assertCopy(descriptor.src, \"src\");\n assertDimension(descriptor.width, \"width\");\n assertDimension(descriptor.height, \"height\");\n if (descriptor.fit !== undefined && !fits.includes(descriptor.fit)) {\n throw new TypeError(`Unsupported Image fit: ${String(descriptor.fit)}`);\n }\n const runtimeDecorative = (\n descriptor as Readonly<{ decorative?: unknown }>\n ).decorative;\n const runtimeLabel = (\n descriptor as Readonly<{ accessibilityLabel?: unknown }>\n ).accessibilityLabel;\n if (\n runtimeDecorative !== undefined &&\n runtimeDecorative !== true &&\n runtimeDecorative !== false\n ) {\n throw new TypeError(\"Image decorative must be a boolean when provided\");\n }\n if (runtimeDecorative === false) {\n if (typeof runtimeLabel !== \"string\" || runtimeLabel.trim().length === 0) {\n throw new TypeError(\n \"Informative Image accessibilityLabel must not be empty\",\n );\n }\n } else if (runtimeLabel !== undefined) {\n throw new TypeError(\"Decorative Image must not provide accessibilityLabel\");\n }\n}\n\nexport function resolveImageDescriptor(\n descriptor: ImageDescriptor,\n): ResolvedImageDescriptor {\n validateImageDescriptor(descriptor);\n const common = {\n src: descriptor.src,\n width: descriptor.width,\n height: descriptor.height,\n fit: descriptor.fit ?? imageDefaults.fit,\n } as const;\n if (descriptor.decorative === false) {\n return {\n ...common,\n decorative: false,\n accessibilityLabel: descriptor.accessibilityLabel,\n } as ResolvedImageDescriptor;\n }\n return { ...common, decorative: true } as ResolvedImageDescriptor;\n}\n\n/**\n * Reserves layout space ahead of load so a slow asset never shifts\n * surrounding content — the same problem `width`/`height` solve for\n * `next/image`, translated without a bundler dependency.\n */\nexport function resolveImageAspectRatio(width: number, height: number): number {\n assertDimension(width, \"width\");\n assertDimension(height, \"height\");\n return width / height;\n}\n\n/**\n * The fallback node on `error` always carries the resolved\n * `accessibilityLabel`, never a generic \"broken image\" string. An\n * informative picture's meaning does not disappear because the asset\n * failed to load; only its visual form changes.\n */\nexport function resolveImageFallbackAccessibilityLabel(\n descriptor: ResolvedImageDescriptor,\n): string | undefined {\n return descriptor.decorative ? undefined : descriptor.accessibilityLabel;\n}\n\nconst errorIconName: SemanticIconName = \"error\";\nconst fallbackIconTone: IconTone = \"secondary\";\n\n/**\n * Web `object-fit` and RN `resizeMode` diverge only in the `fill` case;\n * `fills` is the platform-neutral name and `nativeResizeModes` is the one\n * translation renderers need, kept here instead of re-derived per product.\n */\nexport const nativeResizeModes = {\n cover: \"cover\",\n contain: \"contain\",\n fill: \"stretch\",\n} as const satisfies Record<ImageFit, string>;\n\nexport const imageRecipe = {\n slots: [\"root\", \"image\", \"placeholder\", \"fallbackIcon\"] as const,\n defaults: imageDefaults,\n fits,\n placeholder: {\n background: semanticColors.canvas,\n },\n fallback: {\n background: semanticColors.canvas,\n icon: { name: errorIconName, tone: fallbackIconTone },\n },\n radius: \"md\",\n} as const;\n\n/**\n * Press target that opens ImageViewer from product media (ImageViewerTrigger). 2026-10-23: the portfolio site\n * (`App.tsx` raw button) and Spint (`MarkPhoto`, `ComposeScreen` Pressable + Image) each wrapped an image to open the\n * viewer, with their own name, focus ring and cursor. The trigger owns the button semantics, the image-frame\n * radius for the focus ring, the zoom cursor and the pressed opacity; the media stays decorative inside it.\n */\nexport const imageViewerTriggerRecipe = {\n radius: imageRecipe.radius,\n cursor: \"zoom-in\",\n pressedOpacity: opacity.pressed,\n disabledOpacity: opacity.disabled,\n} as const;\n\nexport function validateImageViewerTriggerLabel(label: string): void {\n if (typeof label !== \"string\" || label.trim().length === 0) throw new TypeError(\"ImageViewerTrigger needs a localized label\");\n}\n\n/**\n * Circular profile-photo crop (2026-10-29 user rule: profile photos are framed in a circle like Instagram before\n * upload). Web and Native draw the same square viewport with a circular mask; the product crops the returned\n * source-pixel square and uploads it. The square keeps the circle's bounding box, so a cover-fitted avatar shows\n * exactly the framed circle.\n */\nexport const circularPhotoCropRecipe = {\n /** Largest zoom over the cover scale; beyond this a small photo turns into blur. */\n maxZoom: 4,\n /** Mask outside the circle (on the black editing surface). */\n maskOpacity: 0.6,\n /** Hairline around the circle so its edge reads on dark and light photos. */\n ringWidth: 1,\n /** Horizontal space kept around the viewport, in px/pt. */\n gutter: 24,\n} as const;\n\nexport type CircularPhotoCropState = Readonly<{ zoom: number; x: number; y: number }>;\nexport type CircularPhotoCropRect = Readonly<{ originX: number; originY: number; width: number; height: number }>;\n\n/** Display px per source px at zoom 1: the photo covers the viewport on its short side. */\nexport function circularPhotoCoverScale(sourceWidth: number, sourceHeight: number, viewport: number): number {\n if (!(sourceWidth > 0 && sourceHeight > 0 && viewport > 0)) throw new RangeError(\"Crop needs positive source and viewport sizes\");\n return viewport / Math.min(sourceWidth, sourceHeight);\n}\n\n/**\n * Keeps the photo covering the viewport: zoom within [1, maxZoom] and the offset (photo centre from viewport\n * centre, display px) within the overhang, so the circle never shows empty space.\n */\nexport function clampCircularPhotoCrop(state: CircularPhotoCropState, sourceWidth: number, sourceHeight: number, viewport: number): CircularPhotoCropState {\n const zoom = Math.min(circularPhotoCropRecipe.maxZoom, Math.max(1, state.zoom));\n const scale = circularPhotoCoverScale(sourceWidth, sourceHeight, viewport) * zoom;\n const limitX = Math.max(0, (sourceWidth * scale - viewport) / 2);\n const limitY = Math.max(0, (sourceHeight * scale - viewport) / 2);\n return { zoom, x: Math.min(limitX, Math.max(-limitX, state.x)), y: Math.min(limitY, Math.max(-limitY, state.y)) };\n}\n\n/** Source-pixel square under the viewport, rounded inside the photo bounds. */\nexport function resolveCircularPhotoCrop(state: CircularPhotoCropState, sourceWidth: number, sourceHeight: number, viewport: number): CircularPhotoCropRect {\n const clamped = clampCircularPhotoCrop(state, sourceWidth, sourceHeight, viewport);\n const scale = circularPhotoCoverScale(sourceWidth, sourceHeight, viewport) * clamped.zoom;\n const size = Math.min(sourceWidth, sourceHeight, Math.round(viewport / scale));\n const centreX = sourceWidth / 2 - clamped.x / scale;\n const centreY = sourceHeight / 2 - clamped.y / scale;\n const originX = Math.min(sourceWidth - size, Math.max(0, Math.round(centreX - size / 2)));\n const originY = Math.min(sourceHeight - size, Math.max(0, Math.round(centreY - size / 2)));\n return { originX, originY, width: size, height: size };\n}\n"]}
|
package/dist/layout.d.ts
CHANGED
|
@@ -23,6 +23,12 @@ export type LayoutDescriptor = Readonly<{
|
|
|
23
23
|
sidebar?: LayoutSidebarDescriptor;
|
|
24
24
|
/** Web-only: required whenever a header or sidebar precedes `main` (WCAG 2.4.1 bypass blocks). */
|
|
25
25
|
skipLinkLabel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Web-only explicit opt-out: `false` renders no skip link and relies on the shell's landmarks (`nav`, `main`) for
|
|
28
|
+
* bypass (WCAG technique ARIA11). For products that removed the link by a recorded product decision (Diairy,
|
|
29
|
+
* 2026-10-02); the default still requires `skipLinkLabel`. Cannot be combined with `skipLinkLabel`.
|
|
30
|
+
*/
|
|
31
|
+
skipLink?: false;
|
|
26
32
|
}>;
|
|
27
33
|
/**
|
|
28
34
|
* Cross-platform structure validation. This deliberately does not require a
|
|
@@ -53,6 +59,10 @@ export declare const layoutRecipe: {
|
|
|
53
59
|
readonly maxWidth: 1200;
|
|
54
60
|
readonly paddingHorizontal: 20;
|
|
55
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* @deprecated Layout no longer applies this width (2.2): the persistent column takes its content's width, and
|
|
64
|
+
* Sidebar owns its steps (`sidebarRecipe.sizes`). Kept for 2.x compatibility of the exported recipe shape.
|
|
65
|
+
*/
|
|
56
66
|
readonly sidebar: {
|
|
57
67
|
readonly width: 280;
|
|
58
68
|
};
|
package/dist/layout.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAMA;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,eAAe,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,SAAS,CAAC;AAEzD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,0GAA0G;IAC1G,IAAI,EAAE,iBAAiB,CAAC;IACxB,gIAAgI;IAChI,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC,kGAAkG;IAClG,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAMA;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,eAAe,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,SAAS,CAAC;AAEzD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,0GAA0G;IAC1G,IAAI,EAAE,iBAAiB,CAAC;IACxB,gIAAgI;IAChI,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC,kGAAkG;IAClG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB,CAAC,CAAC;AAQH;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAcxE;AAED,+EAA+E;AAC/E,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAc9E;AAED,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,GACR,YAAY,GACZ,eAAe,GACf,MAAM,GACN,aAAa,CAAC;AAElB;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,gBAAgB,GAC3B,SAAS,kBAAkB,EAAE,CAQ/B;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY;;;;;;;IAOvB;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyBJ,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;CAuBU,CAAC"}
|
package/dist/layout.js
CHANGED
|
@@ -29,7 +29,13 @@ export function validateLayoutRegions(descriptor) {
|
|
|
29
29
|
export function validateLayoutWebDescriptor(descriptor) {
|
|
30
30
|
validateLayoutRegions(descriptor);
|
|
31
31
|
const needsSkipLink = descriptor.hasHeader === true || descriptor.sidebar !== undefined;
|
|
32
|
-
if (
|
|
32
|
+
if (descriptor.skipLink === false && descriptor.skipLinkLabel !== undefined) {
|
|
33
|
+
throw new TypeError("Layout skipLink={false} cannot be combined with skipLinkLabel");
|
|
34
|
+
}
|
|
35
|
+
if (descriptor.skipLink !== undefined && descriptor.skipLink !== false) {
|
|
36
|
+
throw new TypeError("Layout skipLink only accepts false");
|
|
37
|
+
}
|
|
38
|
+
if (needsSkipLink && descriptor.skipLinkLabel === undefined && descriptor.skipLink !== false) {
|
|
33
39
|
throw new TypeError("Layout requires skipLinkLabel when a header or sidebar region precedes main");
|
|
34
40
|
}
|
|
35
41
|
}
|
|
@@ -65,6 +71,10 @@ export const layoutRecipe = {
|
|
|
65
71
|
maxWidth: layoutFoundation.contentMaxWidth,
|
|
66
72
|
paddingHorizontal: layoutFoundation.pagePadding.regular,
|
|
67
73
|
},
|
|
74
|
+
/**
|
|
75
|
+
* @deprecated Layout no longer applies this width (2.2): the persistent column takes its content's width, and
|
|
76
|
+
* Sidebar owns its steps (`sidebarRecipe.sizes`). Kept for 2.x compatibility of the exported recipe shape.
|
|
77
|
+
*/
|
|
68
78
|
sidebar: {
|
|
69
79
|
width: 280,
|
|
70
80
|
},
|
package/dist/layout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { BehaviorContract } from \"./behaviors.js\";\nimport { focusIndicatorContract } from \"./component-contracts.js\";\nimport { layout as layoutFoundation, spacing } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * `Layout` (alias `AppShell`) does NOT own header or footer chrome — that is\n * already `TopBar` (native, beta) and `BottomNavigation` (adaptive, beta).\n * What is left after subtracting those: ordered regions, a sidebar that is\n * either always-visible chrome or a dismissible overlay, and Web-only\n * landmark composition (one `main` plus a required skip link whenever\n * repeated navigation precedes it). The overlay case reuses `SidePanel`\n * unchanged — see docs/layout.md for why this module does not redeclare\n * open/dismiss state.\n */\nexport type LayoutSidebarRole = \"navigation\" | \"complementary\";\nexport type LayoutSidebarMode = \"persistent\" | \"overlay\";\n\nexport type LayoutSidebarDescriptor = Readonly<{\n /** Primary site navigation vs. supplementary content (filters, related items) — independent of `mode`. */\n role: LayoutSidebarRole;\n /** `persistent`: always-visible landmark. `overlay`: a `SidePanel` — Layout does not re-contract its open/dismiss lifecycle. */\n mode: LayoutSidebarMode;\n label: string;\n}>;\n\nexport type LayoutDescriptor = Readonly<{\n hasHeader?: boolean;\n hasFooter?: boolean;\n sidebar?: LayoutSidebarDescriptor;\n /** Web-only: required whenever a header or sidebar precedes `main` (WCAG 2.4.1 bypass blocks). */\n skipLinkLabel?: string;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Layout ${field} must not be empty`);\n }\n}\n\n/**\n * Cross-platform structure validation. This deliberately does not require a\n * skip link: Native has no page-landmark or bypass-link equivalent.\n */\nexport function validateLayoutRegions(descriptor: LayoutDescriptor): void {\n if (descriptor.sidebar !== undefined) {\n const { role, mode, label } = descriptor.sidebar;\n if (role !== \"navigation\" && role !== \"complementary\") {\n throw new TypeError(`Unsupported Layout sidebar role: ${String(role)}`);\n }\n if (mode !== \"persistent\" && mode !== \"overlay\") {\n throw new TypeError(`Unsupported Layout sidebar mode: ${String(mode)}`);\n }\n assertNonEmpty(label, \"sidebar.label\");\n }\n if (descriptor.skipLinkLabel !== undefined) {\n assertNonEmpty(descriptor.skipLinkLabel, \"skipLinkLabel\");\n }\n}\n\n/** Web landmark validation, including the WCAG 2.4.1 bypass-link invariant. */\nexport function validateLayoutWebDescriptor(descriptor: LayoutDescriptor): void {\n validateLayoutRegions(descriptor);\n const needsSkipLink = descriptor.hasHeader === true || descriptor.sidebar !== undefined;\n if (needsSkipLink && descriptor.skipLinkLabel === undefined) {\n throw new TypeError(\n \"Layout requires skipLinkLabel when a header or sidebar region precedes main\",\n );\n }\n}\n\nexport type LayoutLandmarkRole =\n | \"banner\"\n | \"navigation\"\n | \"complementary\"\n | \"main\"\n | \"contentinfo\";\n\n/**\n * Web-only translation: real landmark elements exist there. Native has no\n * landmark-role equivalent at all (`accessibilityRole` covers headings and\n * controls, not page regions) — RN renderers instead rely on visual/DOM\n * order plus `accessibilityViewIsModal` for the overlay sidebar case. That\n * asymmetry is documented, not papered over with a fake mapping.\n */\nexport function resolveLayoutWebLandmarks(\n descriptor: LayoutDescriptor,\n): readonly LayoutLandmarkRole[] {\n validateLayoutWebDescriptor(descriptor);\n const roles: LayoutLandmarkRole[] = [];\n if (descriptor.hasHeader) roles.push(\"banner\");\n if (descriptor.sidebar) roles.push(descriptor.sidebar.role);\n roles.push(\"main\");\n if (descriptor.hasFooter) roles.push(\"contentinfo\");\n return roles;\n}\n\n/**\n * Skip link is hidden until it matters — visible only on keyboard focus.\n * A skip link visible at rest fights identity.md's \"조용한 화면 위에 중요한\n * 순간만 선명하게\": for a mouse/touch user it is noise every screen, for a\n * keyboard user it is the first landmark on the page.\n */\nexport const layoutRecipe = {\n slots: [\"root\", \"skipLink\", \"header\", \"sidebar\", \"main\", \"footer\"] as const,\n defaults: {},\n main: {\n maxWidth: layoutFoundation.contentMaxWidth,\n paddingHorizontal: layoutFoundation.pagePadding.regular,\n },\n sidebar: {\n width: 280,\n },\n skipLink: {\n visibility: \"focus-only\",\n background: semanticColors.action.brand.background,\n color: semanticColors.action.brand.content,\n paddingHorizontal: spacing.md,\n paddingVertical: spacing.sm,\n },\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"skipLink\", \"header\", \"sidebar\", \"main\", \"footer\"];\n defaults: Record<string, never>;\n main: { maxWidth: number; paddingHorizontal: number };\n sidebar: { width: number };\n skipLink: {\n visibility: \"focus-only\";\n background: ColorReference;\n color: ColorReference;\n paddingHorizontal: number;\n paddingVertical: number;\n };\n states: { focus: typeof focusIndicatorContract };\n};\n\n/**\n * `controlled: []` on purpose: an overlay sidebar's open/dismiss lifecycle\n * is a `SidePanel` (`src/side-panel.ts`) composed as-is. Redeclaring\n * `open`/`defaultOpen`/`onOpenChange`/`dismissPolicy` here would be the same\n * mistake DataTable avoided with Pagination/LoadMore — two places owning\n * one piece of state that will drift the first time either one changes.\n */\nexport const layoutBehavior = {\n controlled: [],\n inputs: [\"hasHeader\", \"hasFooter\", \"sidebar\", \"skipLinkLabel\"],\n configuration: {\n \"sidebar.mode\": [\"persistent\", \"overlay\"],\n \"sidebar.role\": [\"navigation\", \"complementary\"],\n },\n stateAxes: {},\n web: {\n roles: [\"banner\", \"navigation\", \"complementary\", \"main\", \"contentinfo\"],\n keyboard: [\"Tab\"],\n focus: \"native\",\n },\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"exactly-one-main-landmark-exists-per-layout\",\n \"web-skip-link-is-required-whenever-a-header-or-sidebar-precedes-main\",\n \"sidebar-role-navigation-or-complementary-is-chosen-independent-of-persistent-or-overlay-mode\",\n \"overlay-sidebar-reuses-sidepanel-open-state-and-dismiss-policy-unchanged\",\n \"header-and-footer-content-is-not-owned-here-compose-topbar-and-bottomnavigation\",\n \"skip-link-is-visually-hidden-until-keyboard-focus-reaches-it\",\n \"native-has-no-landmark-role-equivalent-translation-relies-on-order-and-accessibilityviewismodal\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,MAAM,IAAI,gBAAgB,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAqCtD,SAAS,cAAc,CAAC,KAAa,EAAE,KAAa;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,SAAS,CAAC,UAAU,KAAK,oBAAoB,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAA4B;IAChE,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC;QACjD,IAAI,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,eAAe,EAAE,CAAC;YACtD,MAAM,IAAI,SAAS,CAAC,oCAAoC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,IAAI,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YAChD,MAAM,IAAI,SAAS,CAAC,oCAAoC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,cAAc,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,UAAU,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QAC3C,cAAc,CAAC,UAAU,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,2BAA2B,CAAC,UAA4B;IACtE,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAClC,MAAM,aAAa,GAAG,UAAU,CAAC,SAAS,KAAK,IAAI,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS,CAAC;IACxF,IAAI,UAAU,CAAC,QAAQ,KAAK,KAAK,IAAI,UAAU,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QAC5E,MAAM,IAAI,SAAS,CAAC,+DAA+D,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS,IAAI,UAAU,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QACvE,MAAM,IAAI,SAAS,CAAC,oCAAoC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,aAAa,IAAI,UAAU,CAAC,aAAa,KAAK,SAAS,IAAI,UAAU,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC7F,MAAM,IAAI,SAAS,CACjB,6EAA6E,CAC9E,CAAC;IACJ,CAAC;AACH,CAAC;AASD;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CACvC,UAA4B;IAE5B,2BAA2B,CAAC,UAAU,CAAC,CAAC;IACxC,MAAM,KAAK,GAAyB,EAAE,CAAC;IACvC,IAAI,UAAU,CAAC,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,UAAU,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnB,IAAI,UAAU,CAAC,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAU;IAC3E,QAAQ,EAAE,EAAE;IACZ,IAAI,EAAE;QACJ,QAAQ,EAAE,gBAAgB,CAAC,eAAe;QAC1C,iBAAiB,EAAE,gBAAgB,CAAC,WAAW,CAAC,OAAO;KACxD;IACD;;;OAGG;IACH,OAAO,EAAE;QACP,KAAK,EAAE,GAAG;KACX;IACD,QAAQ,EAAE;QACR,UAAU,EAAE,YAAY;QACxB,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU;QAClD,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO;QAC1C,iBAAiB,EAAE,OAAO,CAAC,EAAE;QAC7B,eAAe,EAAE,OAAO,CAAC,EAAE;KAC5B;IACD,MAAM,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE;CAc1C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,UAAU,EAAE,EAAE;IACd,MAAM,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,CAAC;IAC9D,aAAa,EAAE;QACb,cAAc,EAAE,CAAC,YAAY,EAAE,SAAS,CAAC;QACzC,cAAc,EAAE,CAAC,YAAY,EAAE,eAAe,CAAC;KAChD;IACD,SAAS,EAAE,EAAE;IACb,GAAG,EAAE;QACH,KAAK,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,CAAC;QACvE,QAAQ,EAAE,CAAC,KAAK,CAAC;QACjB,KAAK,EAAE,QAAQ;KAChB;IACD,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC9C,SAAS,EAAE;QACT,6CAA6C;QAC7C,sEAAsE;QACtE,8FAA8F;QAC9F,0EAA0E;QAC1E,iFAAiF;QACjF,8DAA8D;QAC9D,iGAAiG;KAClG;CACkC,CAAC","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { BehaviorContract } from \"./behaviors.js\";\nimport { focusIndicatorContract } from \"./component-contracts.js\";\nimport { layout as layoutFoundation, spacing } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * `Layout` (alias `AppShell`) does NOT own header or footer chrome — that is\n * already `TopBar` (native, beta) and `BottomNavigation` (adaptive, beta).\n * What is left after subtracting those: ordered regions, a sidebar that is\n * either always-visible chrome or a dismissible overlay, and Web-only\n * landmark composition (one `main` plus a required skip link whenever\n * repeated navigation precedes it). The overlay case reuses `SidePanel`\n * unchanged — see docs/layout.md for why this module does not redeclare\n * open/dismiss state.\n */\nexport type LayoutSidebarRole = \"navigation\" | \"complementary\";\nexport type LayoutSidebarMode = \"persistent\" | \"overlay\";\n\nexport type LayoutSidebarDescriptor = Readonly<{\n /** Primary site navigation vs. supplementary content (filters, related items) — independent of `mode`. */\n role: LayoutSidebarRole;\n /** `persistent`: always-visible landmark. `overlay`: a `SidePanel` — Layout does not re-contract its open/dismiss lifecycle. */\n mode: LayoutSidebarMode;\n label: string;\n}>;\n\nexport type LayoutDescriptor = Readonly<{\n hasHeader?: boolean;\n hasFooter?: boolean;\n sidebar?: LayoutSidebarDescriptor;\n /** Web-only: required whenever a header or sidebar precedes `main` (WCAG 2.4.1 bypass blocks). */\n skipLinkLabel?: string;\n /**\n * Web-only explicit opt-out: `false` renders no skip link and relies on the shell's landmarks (`nav`, `main`) for\n * bypass (WCAG technique ARIA11). For products that removed the link by a recorded product decision (Diairy,\n * 2026-10-02); the default still requires `skipLinkLabel`. Cannot be combined with `skipLinkLabel`.\n */\n skipLink?: false;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Layout ${field} must not be empty`);\n }\n}\n\n/**\n * Cross-platform structure validation. This deliberately does not require a\n * skip link: Native has no page-landmark or bypass-link equivalent.\n */\nexport function validateLayoutRegions(descriptor: LayoutDescriptor): void {\n if (descriptor.sidebar !== undefined) {\n const { role, mode, label } = descriptor.sidebar;\n if (role !== \"navigation\" && role !== \"complementary\") {\n throw new TypeError(`Unsupported Layout sidebar role: ${String(role)}`);\n }\n if (mode !== \"persistent\" && mode !== \"overlay\") {\n throw new TypeError(`Unsupported Layout sidebar mode: ${String(mode)}`);\n }\n assertNonEmpty(label, \"sidebar.label\");\n }\n if (descriptor.skipLinkLabel !== undefined) {\n assertNonEmpty(descriptor.skipLinkLabel, \"skipLinkLabel\");\n }\n}\n\n/** Web landmark validation, including the WCAG 2.4.1 bypass-link invariant. */\nexport function validateLayoutWebDescriptor(descriptor: LayoutDescriptor): void {\n validateLayoutRegions(descriptor);\n const needsSkipLink = descriptor.hasHeader === true || descriptor.sidebar !== undefined;\n if (descriptor.skipLink === false && descriptor.skipLinkLabel !== undefined) {\n throw new TypeError(\"Layout skipLink={false} cannot be combined with skipLinkLabel\");\n }\n if (descriptor.skipLink !== undefined && descriptor.skipLink !== false) {\n throw new TypeError(\"Layout skipLink only accepts false\");\n }\n if (needsSkipLink && descriptor.skipLinkLabel === undefined && descriptor.skipLink !== false) {\n throw new TypeError(\n \"Layout requires skipLinkLabel when a header or sidebar region precedes main\",\n );\n }\n}\n\nexport type LayoutLandmarkRole =\n | \"banner\"\n | \"navigation\"\n | \"complementary\"\n | \"main\"\n | \"contentinfo\";\n\n/**\n * Web-only translation: real landmark elements exist there. Native has no\n * landmark-role equivalent at all (`accessibilityRole` covers headings and\n * controls, not page regions) — RN renderers instead rely on visual/DOM\n * order plus `accessibilityViewIsModal` for the overlay sidebar case. That\n * asymmetry is documented, not papered over with a fake mapping.\n */\nexport function resolveLayoutWebLandmarks(\n descriptor: LayoutDescriptor,\n): readonly LayoutLandmarkRole[] {\n validateLayoutWebDescriptor(descriptor);\n const roles: LayoutLandmarkRole[] = [];\n if (descriptor.hasHeader) roles.push(\"banner\");\n if (descriptor.sidebar) roles.push(descriptor.sidebar.role);\n roles.push(\"main\");\n if (descriptor.hasFooter) roles.push(\"contentinfo\");\n return roles;\n}\n\n/**\n * Skip link is hidden until it matters — visible only on keyboard focus.\n * A skip link visible at rest fights identity.md's \"조용한 화면 위에 중요한\n * 순간만 선명하게\": for a mouse/touch user it is noise every screen, for a\n * keyboard user it is the first landmark on the page.\n */\nexport const layoutRecipe = {\n slots: [\"root\", \"skipLink\", \"header\", \"sidebar\", \"main\", \"footer\"] as const,\n defaults: {},\n main: {\n maxWidth: layoutFoundation.contentMaxWidth,\n paddingHorizontal: layoutFoundation.pagePadding.regular,\n },\n /**\n * @deprecated Layout no longer applies this width (2.2): the persistent column takes its content's width, and\n * Sidebar owns its steps (`sidebarRecipe.sizes`). Kept for 2.x compatibility of the exported recipe shape.\n */\n sidebar: {\n width: 280,\n },\n skipLink: {\n visibility: \"focus-only\",\n background: semanticColors.action.brand.background,\n color: semanticColors.action.brand.content,\n paddingHorizontal: spacing.md,\n paddingVertical: spacing.sm,\n },\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"skipLink\", \"header\", \"sidebar\", \"main\", \"footer\"];\n defaults: Record<string, never>;\n main: { maxWidth: number; paddingHorizontal: number };\n sidebar: { width: number };\n skipLink: {\n visibility: \"focus-only\";\n background: ColorReference;\n color: ColorReference;\n paddingHorizontal: number;\n paddingVertical: number;\n };\n states: { focus: typeof focusIndicatorContract };\n};\n\n/**\n * `controlled: []` on purpose: an overlay sidebar's open/dismiss lifecycle\n * is a `SidePanel` (`src/side-panel.ts`) composed as-is. Redeclaring\n * `open`/`defaultOpen`/`onOpenChange`/`dismissPolicy` here would be the same\n * mistake DataTable avoided with Pagination/LoadMore — two places owning\n * one piece of state that will drift the first time either one changes.\n */\nexport const layoutBehavior = {\n controlled: [],\n inputs: [\"hasHeader\", \"hasFooter\", \"sidebar\", \"skipLinkLabel\"],\n configuration: {\n \"sidebar.mode\": [\"persistent\", \"overlay\"],\n \"sidebar.role\": [\"navigation\", \"complementary\"],\n },\n stateAxes: {},\n web: {\n roles: [\"banner\", \"navigation\", \"complementary\", \"main\", \"contentinfo\"],\n keyboard: [\"Tab\"],\n focus: \"native\",\n },\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"exactly-one-main-landmark-exists-per-layout\",\n \"web-skip-link-is-required-whenever-a-header-or-sidebar-precedes-main\",\n \"sidebar-role-navigation-or-complementary-is-chosen-independent-of-persistent-or-overlay-mode\",\n \"overlay-sidebar-reuses-sidepanel-open-state-and-dismiss-policy-unchanged\",\n \"header-and-footer-content-is-not-owned-here-compose-topbar-and-bottomnavigation\",\n \"skip-link-is-visually-hidden-until-keyboard-focus-reaches-it\",\n \"native-has-no-landmark-role-equivalent-translation-relies-on-order-and-accessibilityviewismodal\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
package/dist/tag.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type ColorReferencePalette } from "./color-references.js";
|
|
2
2
|
export type TagTone = "neutral" | "info" | "success" | "attention" | "brand";
|
|
3
|
+
export type TagVariant = "default" | "outline";
|
|
3
4
|
export declare const tagDefaults: {
|
|
4
5
|
readonly tone: "neutral";
|
|
6
|
+
readonly variant: "outline";
|
|
5
7
|
};
|
|
6
8
|
/**
|
|
7
9
|
* A single piece of static metadata (a position, a grade, a season). Tag has
|
|
@@ -11,23 +13,21 @@ export declare const tagDefaults: {
|
|
|
11
13
|
export type TagDescriptor = Readonly<{
|
|
12
14
|
label: string;
|
|
13
15
|
tone?: TagTone;
|
|
16
|
+
variant?: TagVariant;
|
|
14
17
|
}>;
|
|
15
18
|
export type ResolvedTagDescriptor = Readonly<{
|
|
16
19
|
label: string;
|
|
17
20
|
tone: TagTone;
|
|
21
|
+
variant: TagVariant;
|
|
18
22
|
}>;
|
|
19
23
|
export declare function validateTagDescriptor(descriptor: TagDescriptor): void;
|
|
20
24
|
export declare function resolveTagDescriptor(descriptor: TagDescriptor): ResolvedTagDescriptor;
|
|
21
|
-
/**
|
|
22
|
-
* Rectangular, not the pill `radius.full` that `chipRecipe`
|
|
23
|
-
* use, so a static Tag never reads as a pressable Chip or a status Badge at a
|
|
24
|
-
* glance. No `states` and no `focus` contract: nothing here is ever pressed
|
|
25
|
-
* or focused.
|
|
26
|
-
*/
|
|
25
|
+
/** Static metadata has no focus or press contract, including the outline capsule. */
|
|
27
26
|
export declare const tagRecipe: {
|
|
28
27
|
readonly slots: readonly ["root", "label"];
|
|
29
28
|
readonly defaults: {
|
|
30
29
|
readonly tone: "neutral";
|
|
30
|
+
readonly variant: "outline";
|
|
31
31
|
};
|
|
32
32
|
readonly tones: {
|
|
33
33
|
readonly neutral: {
|
|
@@ -123,6 +123,19 @@ export declare const tagRecipe: {
|
|
|
123
123
|
readonly textVariant: "caption";
|
|
124
124
|
readonly fontWeight: "500";
|
|
125
125
|
};
|
|
126
|
+
readonly outline: {
|
|
127
|
+
readonly minHeight: 32;
|
|
128
|
+
readonly paddingHorizontal: 10;
|
|
129
|
+
readonly paddingVertical: 6;
|
|
130
|
+
readonly radius: "full";
|
|
131
|
+
readonly fontWeight: "400";
|
|
132
|
+
readonly background: "transparent";
|
|
133
|
+
readonly content: Readonly<{
|
|
134
|
+
source: "theme";
|
|
135
|
+
key: "text";
|
|
136
|
+
alpha?: number;
|
|
137
|
+
}>;
|
|
138
|
+
};
|
|
126
139
|
readonly radius: "sm";
|
|
127
140
|
readonly borderWidth: 1;
|
|
128
141
|
};
|
|
@@ -132,5 +145,5 @@ export type ResolvedTagPresentation = Readonly<{
|
|
|
132
145
|
border: string | null;
|
|
133
146
|
}>;
|
|
134
147
|
/** Resolve the same Tag recipe for non-CSS renderers. */
|
|
135
|
-
export declare function resolveTagPresentation(tone: TagTone, palette: ColorReferencePalette): ResolvedTagPresentation;
|
|
148
|
+
export declare function resolveTagPresentation(tone: TagTone, palette: ColorReferencePalette, variant?: TagVariant): ResolvedTagPresentation;
|
|
136
149
|
//# sourceMappingURL=tag.d.ts.map
|
package/dist/tag.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tag.d.ts","sourceRoot":"","sources":["../src/tag.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAI/B,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AAE7E,eAAO,MAAM,WAAW
|
|
1
|
+
{"version":3,"file":"tag.d.ts","sourceRoot":"","sources":["../src/tag.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAI/B,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AAE7E,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/C,eAAO,MAAM,WAAW;;;CAI6C,CAAC;AAEtE;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,UAAU,CAAC;CACtB,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;CACrB,CAAC,CAAC;AAgBH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,aAAa,GAAG,IAAI,CAWrE;AAED,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,aAAa,GACxB,qBAAqB,CAOvB;AAED,qFAAqF;AACrF,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDZ,CAAC;AAEX,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC,CAAC;AAEH,yDAAyD;AACzD,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,qBAAqB,EAC9B,OAAO,GAAE,UAAgC,GACxC,uBAAuB,CAUzB"}
|
package/dist/tag.js
CHANGED
|
@@ -3,6 +3,8 @@ import { semanticColors } from "./semantic-colors.js";
|
|
|
3
3
|
import { fontWeight, spacing, stroke } from "./foundations.js";
|
|
4
4
|
export const tagDefaults = {
|
|
5
5
|
tone: "neutral",
|
|
6
|
+
// The approved border-only capsule is the shared default (2026-10-29).
|
|
7
|
+
variant: "outline",
|
|
6
8
|
};
|
|
7
9
|
function assertCopy(value, field) {
|
|
8
10
|
if (value.trim().length === 0) {
|
|
@@ -18,6 +20,9 @@ const supportedTones = [
|
|
|
18
20
|
];
|
|
19
21
|
export function validateTagDescriptor(descriptor) {
|
|
20
22
|
assertCopy(descriptor.label, "label");
|
|
23
|
+
if (descriptor.variant !== undefined && !["default", "outline"].includes(descriptor.variant)) {
|
|
24
|
+
throw new TypeError(`Unsupported Tag variant: ${String(descriptor.variant)}`);
|
|
25
|
+
}
|
|
21
26
|
if (descriptor.tone !== undefined &&
|
|
22
27
|
!supportedTones.includes(descriptor.tone)) {
|
|
23
28
|
throw new TypeError(`Unsupported Tag tone: ${String(descriptor.tone)}`);
|
|
@@ -28,14 +33,10 @@ export function resolveTagDescriptor(descriptor) {
|
|
|
28
33
|
return {
|
|
29
34
|
label: descriptor.label,
|
|
30
35
|
tone: descriptor.tone ?? tagDefaults.tone,
|
|
36
|
+
variant: descriptor.variant ?? tagDefaults.variant,
|
|
31
37
|
};
|
|
32
38
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Rectangular, not the pill `radius.full` that `chipRecipe`
|
|
35
|
-
* use, so a static Tag never reads as a pressable Chip or a status Badge at a
|
|
36
|
-
* glance. No `states` and no `focus` contract: nothing here is ever pressed
|
|
37
|
-
* or focused.
|
|
38
|
-
*/
|
|
39
|
+
/** Static metadata has no focus or press contract, including the outline capsule. */
|
|
39
40
|
export const tagRecipe = {
|
|
40
41
|
slots: ["root", "label"],
|
|
41
42
|
defaults: tagDefaults,
|
|
@@ -77,15 +78,25 @@ export const tagRecipe = {
|
|
|
77
78
|
textVariant: "caption",
|
|
78
79
|
fontWeight: fontWeight.medium,
|
|
79
80
|
},
|
|
81
|
+
// Profile fact rails need breathing room without a colored plate (2026-10-29 approved preview).
|
|
82
|
+
outline: {
|
|
83
|
+
minHeight: 32,
|
|
84
|
+
paddingHorizontal: 10,
|
|
85
|
+
paddingVertical: 6,
|
|
86
|
+
radius: "full",
|
|
87
|
+
fontWeight: fontWeight.regular,
|
|
88
|
+
background: "transparent",
|
|
89
|
+
content: semanticColors.content.primary,
|
|
90
|
+
},
|
|
80
91
|
radius: "sm",
|
|
81
92
|
borderWidth: stroke.default,
|
|
82
93
|
};
|
|
83
94
|
/** Resolve the same Tag recipe for non-CSS renderers. */
|
|
84
|
-
export function resolveTagPresentation(tone, palette) {
|
|
95
|
+
export function resolveTagPresentation(tone, palette, variant = tagDefaults.variant) {
|
|
85
96
|
const contract = tagRecipe.tones[tone];
|
|
86
97
|
return {
|
|
87
|
-
background: resolveColorReference(contract.background, palette),
|
|
88
|
-
content: resolveColorReference(contract.content, palette),
|
|
98
|
+
background: variant === "outline" ? "transparent" : resolveColorReference(contract.background, palette),
|
|
99
|
+
content: resolveColorReference(variant === "outline" && tone === "neutral" ? tagRecipe.outline.content : contract.content, palette),
|
|
89
100
|
border: contract.border === null
|
|
90
101
|
? null
|
|
91
102
|
: resolveColorReference(contract.border, palette),
|
package/dist/tag.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tag.js","sourceRoot":"","sources":["../src/tag.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,GAEtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"tag.js","sourceRoot":"","sources":["../src/tag.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveColorReference,\n type ColorReferencePalette,\n} from \"./color-references.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport { fontWeight, spacing, stroke } from \"./foundations.js\";\n\nexport type TagTone = \"neutral\" | \"info\" | \"success\" | \"attention\" | \"brand\";\n\nexport type TagVariant = \"default\" | \"outline\";\n\nexport const tagDefaults = {\n tone: \"neutral\",\n // The approved border-only capsule is the shared default (2026-10-29).\n variant: \"outline\",\n} as const satisfies Readonly<{ tone: TagTone; variant: TagVariant }>;\n\n/**\n * A single piece of static metadata (a position, a grade, a season). Tag has\n * no interaction axis at all — no selected, no pressed, no closable. Removing\n * or toggling a tag is deselection, which belongs to `Chip`.\n */\nexport type TagDescriptor = Readonly<{\n label: string;\n tone?: TagTone;\n variant?: TagVariant;\n}>;\n\nexport type ResolvedTagDescriptor = Readonly<{\n label: string;\n tone: TagTone;\n variant: TagVariant;\n}>;\n\nfunction assertCopy(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Tag ${field} must not be empty`);\n }\n}\n\nconst supportedTones: readonly TagTone[] = [\n \"neutral\",\n \"info\",\n \"success\",\n \"attention\",\n \"brand\",\n];\n\nexport function validateTagDescriptor(descriptor: TagDescriptor): void {\n assertCopy(descriptor.label, \"label\");\n if (descriptor.variant !== undefined && ![\"default\", \"outline\"].includes(descriptor.variant)) {\n throw new TypeError(`Unsupported Tag variant: ${String(descriptor.variant)}`);\n }\n if (\n descriptor.tone !== undefined &&\n !supportedTones.includes(descriptor.tone)\n ) {\n throw new TypeError(`Unsupported Tag tone: ${String(descriptor.tone)}`);\n }\n}\n\nexport function resolveTagDescriptor(\n descriptor: TagDescriptor,\n): ResolvedTagDescriptor {\n validateTagDescriptor(descriptor);\n return {\n label: descriptor.label,\n tone: descriptor.tone ?? tagDefaults.tone,\n variant: descriptor.variant ?? tagDefaults.variant,\n };\n}\n\n/** Static metadata has no focus or press contract, including the outline capsule. */\nexport const tagRecipe = {\n slots: [\"root\", \"label\"] as const,\n defaults: tagDefaults,\n tones: {\n // Same as badgeRecipe: a canvas plate without a border vanished on the canvas (2026-10-17 review E3).\n neutral: {\n background: semanticColors.canvas,\n content: semanticColors.content.secondary,\n border: semanticColors.border.default,\n },\n brand: {\n background: semanticColors.canvas,\n content: semanticColors.content.brand,\n border: semanticColors.border.default,\n },\n info: {\n background: semanticColors.canvas,\n content: semanticColors.feedback.info.foreground,\n border: semanticColors.feedback.info.border,\n },\n success: {\n background: semanticColors.feedback.success.badgeBackground,\n content: semanticColors.feedback.success.foreground,\n border: semanticColors.feedback.success.border,\n },\n attention: {\n background: semanticColors.feedback.attention.badgeBackground,\n content: semanticColors.feedback.attention.foreground,\n border: semanticColors.feedback.attention.border,\n },\n },\n size: {\n // Static metadata still participates in Dynamic Type. A fixed 20px frame\n // clips the label at accessibility sizes; minHeight preserves the compact\n // base shape while allowing the intrinsic line box to grow.\n minHeight: 20,\n paddingHorizontal: spacing.xxs,\n gap: spacing.xxs,\n textVariant: \"caption\",\n fontWeight: fontWeight.medium,\n },\n // Profile fact rails need breathing room without a colored plate (2026-10-29 approved preview).\n outline: {\n minHeight: 32,\n paddingHorizontal: 10,\n paddingVertical: 6,\n radius: \"full\",\n fontWeight: fontWeight.regular,\n background: \"transparent\",\n content: semanticColors.content.primary,\n },\n radius: \"sm\",\n borderWidth: stroke.default,\n} as const;\n\nexport type ResolvedTagPresentation = Readonly<{\n background: string;\n content: string;\n border: string | null;\n}>;\n\n/** Resolve the same Tag recipe for non-CSS renderers. */\nexport function resolveTagPresentation(\n tone: TagTone,\n palette: ColorReferencePalette,\n variant: TagVariant = tagDefaults.variant,\n): ResolvedTagPresentation {\n const contract = tagRecipe.tones[tone];\n return {\n background: variant === \"outline\" ? \"transparent\" : resolveColorReference(contract.background, palette),\n content: resolveColorReference(variant === \"outline\" && tone === \"neutral\" ? tagRecipe.outline.content : contract.content, palette),\n border:\n contract.border === null\n ? null\n : resolveColorReference(contract.border, palette),\n };\n}\n"]}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAgB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"2.
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAgB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"2.2.0\" as const;\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Component maturity and evidence manifest
|
|
2
2
|
|
|
3
|
-
> Generated from `@hjmds/design-contracts` 2.
|
|
3
|
+
> Generated from `@hjmds/design-contracts` 2.2.0. Do not edit this file directly; run `pnpm contracts:sync`.
|
|
4
4
|
|
|
5
5
|
This document is the human-readable projection of the same catalog and Showcase manifest consumed by product CI.
|
|
6
6
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"designSystemVersion": "2.
|
|
3
|
+
"designSystemVersion": "2.2.0",
|
|
4
4
|
"source": "packages/design-contracts/docs/generated/showcase-manifest.json",
|
|
5
5
|
"surfaces": {
|
|
6
6
|
"web": {
|
|
7
7
|
"packageName": "@hjmds/react",
|
|
8
|
-
"packageVersion": "2.
|
|
8
|
+
"packageVersion": "2.2.0",
|
|
9
9
|
"activeComponents": 132,
|
|
10
10
|
"claimedComponents": 132,
|
|
11
11
|
"completeScenarioComponents": 131,
|
|
@@ -7114,7 +7114,7 @@
|
|
|
7114
7114
|
},
|
|
7115
7115
|
"native": {
|
|
7116
7116
|
"packageName": "@hjmds/react-native",
|
|
7117
|
-
"packageVersion": "2.
|
|
7117
|
+
"packageVersion": "2.2.0",
|
|
7118
7118
|
"activeComponents": 115,
|
|
7119
7119
|
"claimedComponents": 115,
|
|
7120
7120
|
"completeScenarioComponents": 112,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Renderer evidence coverage
|
|
2
2
|
|
|
3
|
-
> Generated for HJM 2.
|
|
3
|
+
> Generated for HJM 2.2.0. Do not edit directly; run `pnpm evidence:sync`.
|
|
4
4
|
|
|
5
5
|
This projection joins the canonical surface maturity manifest with first-party renderer claims. Missing scenarios are explicit beta promotion debt; stable surfaces are blocked by CI until none remain.
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"designSystemVersion": "2.
|
|
3
|
+
"designSystemVersion": "2.2.0",
|
|
4
4
|
"packageName": "@hjmds/design-contracts",
|
|
5
5
|
"components": [
|
|
6
6
|
{
|
|
@@ -4295,7 +4295,8 @@
|
|
|
4295
4295
|
"extensions": [
|
|
4296
4296
|
"GridReveal",
|
|
4297
4297
|
"ImageViewer",
|
|
4298
|
-
"ImageViewerTrigger"
|
|
4298
|
+
"ImageViewerTrigger",
|
|
4299
|
+
"CircularPhotoCropper"
|
|
4299
4300
|
],
|
|
4300
4301
|
"category": "data-display",
|
|
4301
4302
|
"platform": "shared",
|
package/docs/layout.md
CHANGED
|
@@ -20,7 +20,9 @@ stable)이다. `Layout`이 그 콘텐츠나 상태를 다시 계약하면 두
|
|
|
20
20
|
랜드마크는 **정확히 하나**여야 하고, 헤더나 사이드바처럼 반복되는 내비게이션이
|
|
21
21
|
`main` 앞에 있으면 WCAG 2.4.1(Bypass Blocks)에 따라 skip link가 있어야 한다.
|
|
22
22
|
Web 전용 `validateLayoutWebDescriptor`가 이 규칙을 강제한다 — `hasHeader`나
|
|
23
|
-
`sidebar`가 있는데 `skipLinkLabel`이 없으면 던진다.
|
|
23
|
+
`sidebar`가 있는데 `skipLinkLabel`이 없으면 던진다. 예외는 명시적 `skipLink: false`뿐이다 — skip link 없이
|
|
24
|
+
랜드마크(`nav`·`main`)로 건너뛰기를 제공한다(WCAG 기법 ARIA11). 제품이 기록된 결정으로 링크를 없앤 경우를 위한
|
|
25
|
+
것이다(다에리 2026-10-02 사용자 결정, 2026-10-30 HJM Layout 채택 때 추가). `skipLinkLabel`과 함께 쓰면 던진다. 공통
|
|
24
26
|
`validateLayoutRegions`는 region/sidebar 구조만 검사하므로, bypass-link 개념이
|
|
25
27
|
없는 Native에 Web 요구를 강제하지 않는다. 기존 `validateLayoutDescriptor` 호환 alias는 2.0에서 제거했다.
|
|
26
28
|
- **플랫폼 번역**: 성립하지만 비대칭적으로 성립한다. Web은 실제 랜드마크
|
package/docs/tag.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
`2026 시즌` — 에 일관된 이름과 톤을 붙입니다. 이 라벨은 누를 수 없고, 선택되지도 않고,
|
|
5
5
|
지워지지도 않습니다.
|
|
6
6
|
|
|
7
|
-
**일반화한 계약.** 필수 `label`과 선택적 `tone
|
|
7
|
+
**일반화한 계약.** 필수 `label`과 선택적 `tone`·`variant`를 받는 표현 계약입니다(Statistic처럼
|
|
8
8
|
제품이 완성한 문자열을 받습니다). id, collection 멤버십, 선택 state는 없습니다 — Tag는
|
|
9
9
|
Collection 기본 계약의 대상이 아니라 낱개 표시 단위입니다.
|
|
10
10
|
|
|
@@ -28,9 +28,10 @@ Notice/Badge의 역할이고, Tag는 사실을 나열할 뿐 위협하지 않습
|
|
|
28
28
|
- `closable`(지울 수 있는 태그)은 이 계약에 **없습니다**. 태그를 지우는 것은 선택 해제이고,
|
|
29
29
|
선택 해제는 Chip의 `selected` 축이 이미 소유한 문제입니다. Tag에 별도의 dismiss 축을
|
|
30
30
|
추가하면 같은 "선택 해제"를 두 컴포넌트가 서로 다른 이름으로 계약하게 됩니다.
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
- 기본은 `variant="outline"`인 배경 없는 capsule(`full`)입니다.
|
|
32
|
+
작은 사각형은 명시적인 `variant="default"`로 유지합니다.
|
|
33
|
+
2026-10-29 선수 프로필 정보 시안 승인에 따라 선택 UI와 분리된 읽기 전용 형태로 추가했습니다.
|
|
34
|
+
형태와 무관하게 버튼 role·선택 상태·포커스·활성화 이벤트를 추가하지 않습니다.
|
|
34
35
|
- 야잘알의 `AppBadge`가 이 역할(포지션·등급·시즌 라벨)을 수행한 제품 근거에서 Tag 계약을
|
|
35
36
|
추출했습니다. Web/RN 공식 renderer는 이 계약을 직접 소비하며, 제품의 기존 이름을 Tag로
|
|
36
37
|
바꾸는 일 자체는 소비 앱의 후속 migration입니다.
|
|
@@ -42,7 +43,7 @@ keyboard, focus, accessibilityState 계약이 필요 없습니다 — 접근성
|
|
|
42
43
|
노드 하나로 충분합니다. 여러 Tag를 한 줄에 나열할 때 간격 조합은 나중에 Stack/Inline
|
|
43
44
|
recipe가 안정화되면 그쪽에 위임하고, 이 계약에는 넣지 않습니다.
|
|
44
45
|
|
|
45
|
-
기본 높이는
|
|
46
|
+
기본 최소 높이는 32px이며 고정 높이가 아닙니다. Dynamic Type에서 한 줄 라벨의 고유 높이만큼
|
|
46
47
|
늘어나며, renderer는 라벨을 고정 프레임 안에서 자르지 않습니다.
|
|
47
48
|
|
|
48
49
|
## 현재 maturity와 남은 증거
|
|
@@ -54,3 +55,11 @@ scenario matrix가 dark(라이트 전용 색 누수 없음), 2배 글자, RTL,
|
|
|
54
55
|
|
|
55
56
|
stable은 전체 환경 인증을 뜻하지 않습니다. screen reader 실사용과 실제 device screenshot은
|
|
56
57
|
제품 기기 QA의 범위입니다.
|
|
58
|
+
|
|
59
|
+
## Outline 표현
|
|
60
|
+
|
|
61
|
+
`variant`는 `default | outline`, 기본값은 `outline`입니다.
|
|
62
|
+
2026-10-29 사용자가 승인한 테두리형을 기본으로 지정했습니다. Outline은 투명 배경·1px 테두리·
|
|
63
|
+
`radius.full`, 최소 높이 32, 좌우 10·상하 6, caption regular(400)입니다.
|
|
64
|
+
neutral 글자는 `content.primary`, 다른 tone은 기존 글자·테두리 의미를 유지합니다.
|
|
65
|
+
흰 배경 전용 색을 하드코딩하지 않고 Provider의 light/dark 및 제품 팔레트를 따릅니다.
|