@hjmds/design-contracts 1.30.0 → 1.32.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/behaviors.d.ts +3 -0
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/bottom-navigation.d.ts +43 -0
- package/dist/bottom-navigation.d.ts.map +1 -1
- package/dist/bottom-navigation.js +55 -0
- package/dist/bottom-navigation.js.map +1 -1
- package/dist/carousel.d.ts +30 -0
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.js +32 -0
- package/dist/carousel.js.map +1 -1
- package/dist/catalog.d.ts +178 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +16 -6
- package/dist/catalog.js.map +1 -1
- package/dist/component-recipes.d.ts +126 -0
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +71 -2
- package/dist/component-recipes.js.map +1 -1
- package/dist/icon.d.ts +58 -1
- package/dist/icon.d.ts.map +1 -1
- package/dist/icon.js +69 -0
- package/dist/icon.js.map +1 -1
- package/dist/image-choice.d.ts +47 -0
- package/dist/image-choice.d.ts.map +1 -1
- package/dist/image-choice.js +56 -0
- package/dist/image-choice.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/pagination.d.ts +53 -0
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +48 -0
- package/dist/pagination.js.map +1 -1
- package/dist/recipes.d.ts +1 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js.map +1 -1
- package/dist/screen-patterns.d.ts +152 -2
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +132 -4
- package/dist/screen-patterns.js.map +1 -1
- package/dist/showcase.d.ts.map +1 -1
- package/dist/showcase.js +3 -0
- package/dist/showcase.js.map +1 -1
- package/dist/steps.d.ts +16 -7
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +6 -1
- package/dist/steps.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/authoring-tools.md +1 -0
- package/docs/bottom-navigation.md +28 -0
- package/docs/carousel.md +8 -0
- package/docs/data-workflows.md +1 -0
- package/docs/dialog.md +23 -0
- package/docs/generated/component-maturity.md +7 -4
- package/docs/generated/renderer-evidence.json +317 -19
- package/docs/generated/renderer-evidence.md +9 -3
- package/docs/generated/showcase-manifest.json +164 -6
- package/docs/icon.md +9 -0
- package/docs/pagination.md +17 -0
- package/docs/screen-patterns.md +55 -1
- package/docs/steps.md +4 -3
- package/docs/toast.md +10 -0
- package/docs/usage/README.md +5 -2
- package/docs/usage/SELECTION.md +1 -1
- package/docs/usage/components/auth-screen-layout.md +15 -5
- package/docs/usage/components/bottom-cta.md +11 -0
- package/docs/usage/components/bottom-navigation.md +99 -2
- package/docs/usage/components/card.md +36 -5
- package/docs/usage/components/carousel.md +1 -0
- package/docs/usage/components/chat-date-separator.md +86 -0
- package/docs/usage/components/chat-message.md +13 -1
- package/docs/usage/components/chat-screen.md +3 -2
- package/docs/usage/components/comment-thread-row.md +11 -3
- package/docs/usage/components/comment-thread-screen.md +1 -1
- package/docs/usage/components/dialog.md +69 -0
- package/docs/usage/components/editor-screen.md +6 -2
- package/docs/usage/components/field.md +13 -0
- package/docs/usage/components/icon-tile.md +19 -3
- package/docs/usage/components/icon.md +45 -6
- package/docs/usage/components/image-choice-group.md +35 -4
- package/docs/usage/components/media-selection-screen.md +20 -1
- package/docs/usage/components/message-composer.md +4 -1
- package/docs/usage/components/moderation-screen.md +107 -2
- package/docs/usage/components/page-indicator.md +102 -0
- package/docs/usage/components/pagination.md +28 -4
- package/docs/usage/components/profile-screen.md +9 -3
- package/docs/usage/components/profile-summary.md +3 -3
- package/docs/usage/components/screen-layout.md +15 -3
- package/docs/usage/components/search-screen.md +5 -3
- package/docs/usage/components/steps.md +14 -7
- package/docs/usage/components/tabs.md +21 -1
- package/docs/usage/components/text.md +30 -1
- package/docs/usage/components/toast.md +5 -0
- package/docs/usage/components/typing-indicator.md +89 -0
- package/docs/usage/screens/common-login.md +3 -1
- package/docs/usage/screens/common-profile.md +3 -3
- package/docs/usage/screens/flow-editor.md +13 -2
- package/docs/usage/screens/flow-moderation.md +31 -3
- package/package.json +1 -1
|
@@ -53,9 +53,13 @@ export declare function validateScreenExit(exit: Readonly<{
|
|
|
53
53
|
* `body-edge`: header, notice and footer keep the shell padding; only the body's inline padding is 0 so full-width
|
|
54
54
|
* list rows (ConversationRow, ListRow-based items) supply their own inset — review S5#9 measured DM avatars ~32pt
|
|
55
55
|
* from the edge because body padding and row padding stacked (2026-10-17 user decision).
|
|
56
|
+
* `body-bleed`: header, notice and footer keep the shell padding; the body has no gutter on any side, so a map or
|
|
57
|
+
* canvas meets the header (or notice), the screen edges and the footer. With no footer the body is still the last
|
|
58
|
+
* region and clears the bottom insets the screen owns (`safeAreaBottom`, an overlay BottomNavigation) without the
|
|
59
|
+
* gutter. choose-window's map screens used `body-edge` and kept a 16pt strip above and below the map (2026-10-18).
|
|
56
60
|
*/
|
|
57
|
-
export type ScreenContentInset = "default" | "none" | "body-edge";
|
|
58
|
-
export declare const screenContentInsets: readonly ["default", "none", "body-edge"];
|
|
61
|
+
export type ScreenContentInset = "default" | "none" | "body-edge" | "body-bleed";
|
|
62
|
+
export declare const screenContentInsets: readonly ["default", "none", "body-edge", "body-bleed"];
|
|
59
63
|
/**
|
|
60
64
|
* 화면 진입 시 첫 입력 포커스(2026-10-18 사용자 요청: "input이 있는 화면에 진입하면 포커스되도록, 웹앱 둘 다").
|
|
61
65
|
* 앱마다 autoFocus를 따로 붙이던 일을 화면 골격이 맡는다. 입력 칸은 가장 가까운 화면·시트 범위에 자신을 등록하고,
|
|
@@ -212,9 +216,102 @@ export declare const chatMessageRecipe: {
|
|
|
212
216
|
readonly timelineInset: 10;
|
|
213
217
|
readonly mediaWidth: 210;
|
|
214
218
|
readonly mediaGap: 2;
|
|
219
|
+
/**
|
|
220
|
+
* Reaction summary pill beside the time (2026-10-18 BurnTok DM): one button that opens the same hold menu.
|
|
221
|
+
* Counts below `minCount` show the emoji alone, as the measured DM references do for a single reaction.
|
|
222
|
+
*/
|
|
223
|
+
readonly reactionSummary: {
|
|
224
|
+
readonly minHeight: 24;
|
|
225
|
+
readonly paddingInline: 8;
|
|
226
|
+
readonly itemGap: 4;
|
|
227
|
+
readonly emojiGap: 2;
|
|
228
|
+
readonly minCount: 2;
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
export type ChatReactionSummaryItem = Readonly<{
|
|
232
|
+
id: string;
|
|
233
|
+
emoji: string;
|
|
234
|
+
count: number;
|
|
235
|
+
}>;
|
|
236
|
+
export type ChatReactionSummary = Readonly<{
|
|
237
|
+
label: string;
|
|
238
|
+
items: readonly ChatReactionSummaryItem[];
|
|
239
|
+
/**
|
|
240
|
+
* Counts only, for readers who cannot react (2026-10-18 Utilverse guests): drawn in the same place beside the time
|
|
241
|
+
* as non-interactive text named by `label`, without the pill surface, so it does not look or act like a control.
|
|
242
|
+
* Default `false` (the pill button that opens the hold menu).
|
|
243
|
+
*/
|
|
244
|
+
readOnly?: boolean;
|
|
245
|
+
}>;
|
|
246
|
+
/**
|
|
247
|
+
* Validates ChatMessage `reactions.summary` and marks which counts are drawn. The label (the button's accessible name)
|
|
248
|
+
* is required; ids are unique and non-empty, emoji non-empty and counts positive integers; a count below
|
|
249
|
+
* `reactionSummary.minCount` shows the emoji alone.
|
|
250
|
+
*/
|
|
251
|
+
export declare function resolveChatReactionSummary({ label, items }: ChatReactionSummary): readonly (ChatReactionSummaryItem & Readonly<{
|
|
252
|
+
showCount: boolean;
|
|
253
|
+
}>)[];
|
|
254
|
+
/**
|
|
255
|
+
* "Someone is typing" row in a chat timeline (2026-10-18 BurnTok DM). It reuses the incoming bubble geometry
|
|
256
|
+
* (chatMessageRecipe) so it reads as the next incoming message; only the dots are its own values. Under reduced
|
|
257
|
+
* motion the dots stay at full opacity instead of pulsing.
|
|
258
|
+
*/
|
|
259
|
+
export declare const typingIndicatorRecipe: {
|
|
260
|
+
readonly dotSize: 6;
|
|
261
|
+
readonly dotGap: 4;
|
|
262
|
+
/** One pulse of the three dots; each dot starts `staggerMs` after the previous one. */
|
|
263
|
+
readonly cycleMs: 1200;
|
|
264
|
+
readonly staggerMs: 160;
|
|
265
|
+
readonly restOpacity: 0.35;
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* Centered date (or time) row between timeline groups (2026-10-18 Utilverse chat, which drew it as a product
|
|
269
|
+
* caption). The next message still owns its own group gap above it, so this row only adds the space before it.
|
|
270
|
+
*/
|
|
271
|
+
export declare const chatDateSeparatorRecipe: {
|
|
272
|
+
readonly paddingTop: 12;
|
|
273
|
+
readonly paddingBottom: 4;
|
|
274
|
+
readonly textVariant: "caption";
|
|
275
|
+
readonly tone: "muted";
|
|
215
276
|
};
|
|
216
277
|
/** Column count for a photo set: pairs read as 2-up, other counts as 3-up; one photo spans the width. */
|
|
217
278
|
export declare function resolveChatMediaColumns(count: number): 1 | 2 | 3;
|
|
279
|
+
/**
|
|
280
|
+
* Paged photo album for ChatMessage `mediaAlbum` and CommentThreadItem `mediaAlbum` (2026-10-18, Utilverse social
|
|
281
|
+
* photos). Only the shown page is rendered, so a product that authorizes each image can fetch one photo at a time
|
|
282
|
+
* instead of every photo in a timeline row. The counter row matches CarouselMotion `navigation="counter"`:
|
|
283
|
+
* previous icon · position · next icon under the page; the frame keeps the host's media width and radius.
|
|
284
|
+
*/
|
|
285
|
+
export declare const mediaAlbumRecipe: {
|
|
286
|
+
/** Page frame width / height when the product gives no aspect ratio. */
|
|
287
|
+
readonly defaultAspectRatio: 1;
|
|
288
|
+
/** Space between the page frame and the counter row. */
|
|
289
|
+
readonly controlGap: 4;
|
|
290
|
+
/** Previous/next are small ghost IconButtons so the counter row stays compact beside thumbnails. */
|
|
291
|
+
readonly controlSize: "small";
|
|
292
|
+
readonly controlGlyphSize: 20;
|
|
293
|
+
/**
|
|
294
|
+
* Loading / failed page drawn by `MediaAlbumPageStatus` (2026-10-18 Utilverse: the page frame was one accessible
|
|
295
|
+
* image, so the loading text and retry button inside it were unreachable by screen readers). The status block is
|
|
296
|
+
* centered in the frame (VQ-09); retry is a small secondary Button.
|
|
297
|
+
*/
|
|
298
|
+
readonly pageStatus: {
|
|
299
|
+
readonly gap: 8;
|
|
300
|
+
readonly padding: 12;
|
|
301
|
+
readonly textVariant: "caption";
|
|
302
|
+
readonly retryTone: "secondary";
|
|
303
|
+
readonly retrySize: "small";
|
|
304
|
+
};
|
|
305
|
+
};
|
|
306
|
+
export type MediaAlbumPageStatusKind = "loading" | "error";
|
|
307
|
+
/** A page status names what the reader is waiting for or what failed; the label is the only copy. */
|
|
308
|
+
export declare function validateMediaAlbumPageStatus(kind: MediaAlbumPageStatusKind, label: string): void;
|
|
309
|
+
/** A controlled index outside the set is a contract error, like Carousel's unknown `currentKey`. */
|
|
310
|
+
export declare function validateMediaAlbum(count: number, index: number): void;
|
|
311
|
+
/** One message or comment carries one photo set: either the eager `media` previews or the paged `mediaAlbum`. */
|
|
312
|
+
export declare function validateMediaSetChoice(hasMedia: boolean, hasAlbum: boolean): void;
|
|
313
|
+
/** Uncontrolled albums keep the nearest page when the product's photo set shrinks (moderation, deletion). */
|
|
314
|
+
export declare function resolveMediaAlbumIndex(count: number, index: number): number;
|
|
218
315
|
/**
|
|
219
316
|
* Long-press menu geometry measured from the Instagram iOS DM hold menu (2026-10-16 @3x capture):
|
|
220
317
|
* full-width reaction strip ~60pt with ~30pt emoji, the pressed bubble itself as preview, and a ~200pt
|
|
@@ -292,6 +389,7 @@ export declare const commentThreadRecipe: {
|
|
|
292
389
|
readonly likeColumnWidth: 44;
|
|
293
390
|
readonly mediaMaxWidth: 160;
|
|
294
391
|
readonly mediaRadius: 12;
|
|
392
|
+
readonly panelGap: 8;
|
|
295
393
|
readonly menuGlyphSize: 20;
|
|
296
394
|
/**
|
|
297
395
|
* Trailing actions (like, product actions, more) centre their 44 targets on the identity's first line instead of
|
|
@@ -383,6 +481,21 @@ export declare function resolveSearchCommit(value: string): string | null;
|
|
|
383
481
|
*/
|
|
384
482
|
export type SearchEmptyCause = "none" | "filters" | "query";
|
|
385
483
|
export declare function resolveSearchEmptyCause(count: number | null, appliedFilterCount: number): SearchEmptyCause;
|
|
484
|
+
/**
|
|
485
|
+
* What the SearchScreen filter rail does to a result. `narrow` (default) only removes results, so with nothing applied
|
|
486
|
+
* a zero result is the query's and no filter can bring results back. `scope` chooses what is searched (keyword vs
|
|
487
|
+
* address, region, map area): changing it can turn a zero into results even with nothing applied (2026-10-18:
|
|
488
|
+
* choose-window's keyword/address switch is the recovery for a zero result, so it skipped resultSummary entirely).
|
|
489
|
+
*/
|
|
490
|
+
export type SearchFiltersRole = "narrow" | "scope";
|
|
491
|
+
export declare const searchFiltersRoles: readonly ["narrow", "scope"];
|
|
492
|
+
/**
|
|
493
|
+
* Whether SearchScreen shows its filter rail (`filters` and the filter sheet trigger). Two-step search shows it only
|
|
494
|
+
* beside results so typing keeps the pinned area short. A query-caused zero hides it only when the filters narrow,
|
|
495
|
+
* because then none of them can fix the zero; scope filters stay as the recovery path. A filter-caused zero always keeps
|
|
496
|
+
* it so the cause can be undone. Both renderers call this so the rule cannot drift between platforms.
|
|
497
|
+
*/
|
|
498
|
+
export declare function resolveSearchFilterRailVisible(phase: SearchScreenPhase, twoStep: boolean, cause: SearchEmptyCause, role?: SearchFiltersRole): boolean;
|
|
386
499
|
/**
|
|
387
500
|
* Index to focus after removing item `removedIndex` from a list that now has `remaining` items:
|
|
388
501
|
* the item that slid into its place, else the new last item, else -1 (move to the fallback target).
|
|
@@ -426,8 +539,26 @@ export declare const notificationRowRecipe: {
|
|
|
426
539
|
/** Shared profile identity geometry; product owns the avatar content. */
|
|
427
540
|
export declare const profileSummaryRecipe: Readonly<{
|
|
428
541
|
avatarSize: 72;
|
|
542
|
+
/**
|
|
543
|
+
* ProfileScreen `editPlacement="summary"` draws the edit action first in the summary's action row. A framed small
|
|
544
|
+
* button reads as the card's own action; the below-summary placement keeps its ghost button (2026-10-15 utilverse
|
|
545
|
+
* account decision: the edit action belongs inside the profile card).
|
|
546
|
+
*/
|
|
547
|
+
editAction: Readonly<{
|
|
548
|
+
readonly tone: "secondary";
|
|
549
|
+
readonly size: "small";
|
|
550
|
+
}>;
|
|
429
551
|
}>;
|
|
430
552
|
/** Launcher artwork stays above its caption; product overlays may extend beyond the icon frame. */
|
|
553
|
+
/**
|
|
554
|
+
* Frame of an IconTile icon (2026-10-18 Utilverse launcher). `surface` draws the shared filled, hairlined frame;
|
|
555
|
+
* `none` leaves product artwork that is already a complete launcher icon unframed. `shape` follows the product's
|
|
556
|
+
* icon theme (squircle on iPhone shells, square on Windows/Linux shells); `size: "fill"` lets the icon span its
|
|
557
|
+
* grid cell between `fillMinSize` and `fillMaxSize` instead of a product computing the cell size.
|
|
558
|
+
*/
|
|
559
|
+
export type IconTileFrame = "surface" | "none";
|
|
560
|
+
export type IconTileShape = "squircle" | "square" | "circle";
|
|
561
|
+
export type IconTileSize = "fixed" | "fill";
|
|
431
562
|
export declare const iconTileRecipe: Readonly<{
|
|
432
563
|
iconSize: 64;
|
|
433
564
|
labelMaxWidth: 72;
|
|
@@ -436,5 +567,24 @@ export declare const iconTileRecipe: Readonly<{
|
|
|
436
567
|
ghostOpacity: 0.55;
|
|
437
568
|
gap: 6;
|
|
438
569
|
iconRadius: "22%";
|
|
570
|
+
/** Corner radius as a fraction of the icon size; `squircle` keeps the original 22%. */
|
|
571
|
+
shapes: Readonly<Record<IconTileShape, number>>;
|
|
572
|
+
fillMinSize: 48;
|
|
573
|
+
fillMaxSize: 68;
|
|
574
|
+
defaults: Readonly<{
|
|
575
|
+
frame: IconTileFrame;
|
|
576
|
+
shape: IconTileShape;
|
|
577
|
+
size: IconTileSize;
|
|
578
|
+
}>;
|
|
579
|
+
}>;
|
|
580
|
+
/**
|
|
581
|
+
* What an IconTile passes to an `icon` render function: the rendered icon edge (`iconSize` for `fixed`, the measured
|
|
582
|
+
* cell-clamped edge for `fill`). Fixed-size artwork (images, generated launcher art) sizes itself from it; Utilverse
|
|
583
|
+
* re-computed the 48–68 clamp from its grid width to size its artwork (2026-10-18).
|
|
584
|
+
*/
|
|
585
|
+
export type IconTileArtwork = Readonly<{
|
|
586
|
+
edge: number;
|
|
439
587
|
}>;
|
|
588
|
+
/** The edge a `size="fill"` icon takes in a cell of `cellWidth`, for layout estimates made before the tile measures. */
|
|
589
|
+
export declare function resolveIconTileFillEdge(cellWidth: number): number;
|
|
440
590
|
//# sourceMappingURL=screen-patterns.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen-patterns.d.ts","sourceRoot":"","sources":["../src/screen-patterns.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,0FAA0F;AAC1F,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,CAAC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GAC3B,QAAQ,CAAC;IAAE,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE1G,wBAAgB,yBAAyB,CAAC,KAAK,GAAE,kBAAsC;;;;;EAatF;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;IAM7B;;;OAGG;;;CAGK,CAAC;AAEX;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,CAAC;AAC9C,eAAO,MAAM,gBAAgB;IAC3B,gHAAgH;;;;CAIxG,CAAC;AACX,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,UAAU,EAAE,OAAO,QAItG;AAED
|
|
1
|
+
{"version":3,"file":"screen-patterns.d.ts","sourceRoot":"","sources":["../src/screen-patterns.ts"],"names":[],"mappings":"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"}
|
package/dist/screen-patterns.js
CHANGED
|
@@ -46,7 +46,7 @@ export function validateScreenExit(exit, hasLeading) {
|
|
|
46
46
|
if (hasLeading)
|
|
47
47
|
throw new TypeError("Screen exit owns the leading slot; do not also pass leading");
|
|
48
48
|
}
|
|
49
|
-
export const screenContentInsets = ["default", "none", "body-edge"];
|
|
49
|
+
export const screenContentInsets = ["default", "none", "body-edge", "body-bleed"];
|
|
50
50
|
/** Defaults per composition; renderers read these instead of repeating literals. */
|
|
51
51
|
export const initialFocusDefaults = {
|
|
52
52
|
screen: "none",
|
|
@@ -156,6 +156,54 @@ export const chatMessageRecipe = {
|
|
|
156
156
|
// Photos render without the bubble surface (measured reference: ~210pt wide, bubble radius).
|
|
157
157
|
mediaWidth: 210,
|
|
158
158
|
mediaGap: 2,
|
|
159
|
+
/**
|
|
160
|
+
* Reaction summary pill beside the time (2026-10-18 BurnTok DM): one button that opens the same hold menu.
|
|
161
|
+
* Counts below `minCount` show the emoji alone, as the measured DM references do for a single reaction.
|
|
162
|
+
*/
|
|
163
|
+
reactionSummary: { minHeight: 24, paddingInline: spacing.xs, itemGap: spacing.xxs, emojiGap: 2, minCount: 2 },
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Validates ChatMessage `reactions.summary` and marks which counts are drawn. The label (the button's accessible name)
|
|
167
|
+
* is required; ids are unique and non-empty, emoji non-empty and counts positive integers; a count below
|
|
168
|
+
* `reactionSummary.minCount` shows the emoji alone.
|
|
169
|
+
*/
|
|
170
|
+
export function resolveChatReactionSummary({ label, items }) {
|
|
171
|
+
if (!label.trim())
|
|
172
|
+
throw new TypeError("Reaction summary requires a localized label");
|
|
173
|
+
const ids = new Set();
|
|
174
|
+
return items.map(item => {
|
|
175
|
+
if (!item.id.trim() || ids.has(item.id))
|
|
176
|
+
throw new TypeError("Reaction summary ids must be unique and non-empty");
|
|
177
|
+
if (!item.emoji.trim())
|
|
178
|
+
throw new TypeError("Reaction summary items require an emoji");
|
|
179
|
+
if (!Number.isSafeInteger(item.count) || item.count < 1)
|
|
180
|
+
throw new RangeError("Reaction summary counts must be positive integers");
|
|
181
|
+
ids.add(item.id);
|
|
182
|
+
return { ...item, showCount: item.count >= chatMessageRecipe.reactionSummary.minCount };
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* "Someone is typing" row in a chat timeline (2026-10-18 BurnTok DM). It reuses the incoming bubble geometry
|
|
187
|
+
* (chatMessageRecipe) so it reads as the next incoming message; only the dots are its own values. Under reduced
|
|
188
|
+
* motion the dots stay at full opacity instead of pulsing.
|
|
189
|
+
*/
|
|
190
|
+
export const typingIndicatorRecipe = {
|
|
191
|
+
dotSize: 6,
|
|
192
|
+
dotGap: spacing.xxs,
|
|
193
|
+
/** One pulse of the three dots; each dot starts `staggerMs` after the previous one. */
|
|
194
|
+
cycleMs: 1200,
|
|
195
|
+
staggerMs: 160,
|
|
196
|
+
restOpacity: 0.35,
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Centered date (or time) row between timeline groups (2026-10-18 Utilverse chat, which drew it as a product
|
|
200
|
+
* caption). The next message still owns its own group gap above it, so this row only adds the space before it.
|
|
201
|
+
*/
|
|
202
|
+
export const chatDateSeparatorRecipe = {
|
|
203
|
+
paddingTop: spacing.sm,
|
|
204
|
+
paddingBottom: spacing.xxs,
|
|
205
|
+
textVariant: "caption",
|
|
206
|
+
tone: "muted",
|
|
159
207
|
};
|
|
160
208
|
/** Column count for a photo set: pairs read as 2-up, other counts as 3-up; one photo spans the width. */
|
|
161
209
|
export function resolveChatMediaColumns(count) {
|
|
@@ -163,6 +211,52 @@ export function resolveChatMediaColumns(count) {
|
|
|
163
211
|
throw new RangeError("Chat media requires at least one item");
|
|
164
212
|
return count === 1 ? 1 : count === 2 || count === 4 ? 2 : 3;
|
|
165
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Paged photo album for ChatMessage `mediaAlbum` and CommentThreadItem `mediaAlbum` (2026-10-18, Utilverse social
|
|
216
|
+
* photos). Only the shown page is rendered, so a product that authorizes each image can fetch one photo at a time
|
|
217
|
+
* instead of every photo in a timeline row. The counter row matches CarouselMotion `navigation="counter"`:
|
|
218
|
+
* previous icon · position · next icon under the page; the frame keeps the host's media width and radius.
|
|
219
|
+
*/
|
|
220
|
+
export const mediaAlbumRecipe = {
|
|
221
|
+
/** Page frame width / height when the product gives no aspect ratio. */
|
|
222
|
+
defaultAspectRatio: 1,
|
|
223
|
+
/** Space between the page frame and the counter row. */
|
|
224
|
+
controlGap: spacing.xxs,
|
|
225
|
+
/** Previous/next are small ghost IconButtons so the counter row stays compact beside thumbnails. */
|
|
226
|
+
controlSize: "small",
|
|
227
|
+
controlGlyphSize: glyph.sm,
|
|
228
|
+
/**
|
|
229
|
+
* Loading / failed page drawn by `MediaAlbumPageStatus` (2026-10-18 Utilverse: the page frame was one accessible
|
|
230
|
+
* image, so the loading text and retry button inside it were unreachable by screen readers). The status block is
|
|
231
|
+
* centered in the frame (VQ-09); retry is a small secondary Button.
|
|
232
|
+
*/
|
|
233
|
+
pageStatus: { gap: spacing.xs, padding: spacing.sm, textVariant: "caption", retryTone: "secondary", retrySize: "small" },
|
|
234
|
+
};
|
|
235
|
+
/** A page status names what the reader is waiting for or what failed; the label is the only copy. */
|
|
236
|
+
export function validateMediaAlbumPageStatus(kind, label) {
|
|
237
|
+
if (kind !== "loading" && kind !== "error")
|
|
238
|
+
throw new TypeError("Media album page status must be loading or error");
|
|
239
|
+
if (!label.trim())
|
|
240
|
+
throw new TypeError("Media album page status requires a localized label");
|
|
241
|
+
}
|
|
242
|
+
/** A controlled index outside the set is a contract error, like Carousel's unknown `currentKey`. */
|
|
243
|
+
export function validateMediaAlbum(count, index) {
|
|
244
|
+
if (!Number.isSafeInteger(count) || count < 1)
|
|
245
|
+
throw new RangeError("Media album requires at least one page");
|
|
246
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= count)
|
|
247
|
+
throw new RangeError("Media album index must point at an existing page");
|
|
248
|
+
}
|
|
249
|
+
/** One message or comment carries one photo set: either the eager `media` previews or the paged `mediaAlbum`. */
|
|
250
|
+
export function validateMediaSetChoice(hasMedia, hasAlbum) {
|
|
251
|
+
if (hasMedia && hasAlbum)
|
|
252
|
+
throw new TypeError("Pass either media or mediaAlbum, not both");
|
|
253
|
+
}
|
|
254
|
+
/** Uncontrolled albums keep the nearest page when the product's photo set shrinks (moderation, deletion). */
|
|
255
|
+
export function resolveMediaAlbumIndex(count, index) {
|
|
256
|
+
if (!Number.isSafeInteger(count) || count < 1)
|
|
257
|
+
throw new RangeError("Media album requires at least one page");
|
|
258
|
+
return Math.min(Math.max(0, Math.trunc(index)), count - 1);
|
|
259
|
+
}
|
|
166
260
|
/**
|
|
167
261
|
* Long-press menu geometry measured from the Instagram iOS DM hold menu (2026-10-16 @3x capture):
|
|
168
262
|
* full-width reaction strip ~60pt with ~30pt emoji, the pressed bubble itself as preview, and a ~200pt
|
|
@@ -237,6 +331,9 @@ export const commentThreadRecipe = {
|
|
|
237
331
|
// Optional photo/sticker under the body; the measured reference keeps it thumbnail-sized.
|
|
238
332
|
mediaMaxWidth: 160,
|
|
239
333
|
mediaRadius: radius.md,
|
|
334
|
+
// Inline per-comment panel (edit/report/block form) under the action row (2026-10-18, Utilverse): a form reads as its
|
|
335
|
+
// own block, so it sits a full spacing step below the reply line instead of the 2pt line gap.
|
|
336
|
+
panelGap: spacing.xs,
|
|
240
337
|
// The trailing vertical-dots menu glyph; the button keeps the shared 44pt IconButton target.
|
|
241
338
|
menuGlyphSize: glyph.sm,
|
|
242
339
|
/**
|
|
@@ -326,6 +423,20 @@ export function resolveSearchEmptyCause(count, appliedFilterCount) {
|
|
|
326
423
|
return "none";
|
|
327
424
|
return appliedFilterCount > 0 ? "filters" : "query";
|
|
328
425
|
}
|
|
426
|
+
export const searchFiltersRoles = ["narrow", "scope"];
|
|
427
|
+
/**
|
|
428
|
+
* Whether SearchScreen shows its filter rail (`filters` and the filter sheet trigger). Two-step search shows it only
|
|
429
|
+
* beside results so typing keeps the pinned area short. A query-caused zero hides it only when the filters narrow,
|
|
430
|
+
* because then none of them can fix the zero; scope filters stay as the recovery path. A filter-caused zero always keeps
|
|
431
|
+
* it so the cause can be undone. Both renderers call this so the rule cannot drift between platforms.
|
|
432
|
+
*/
|
|
433
|
+
export function resolveSearchFilterRailVisible(phase, twoStep, cause, role = "narrow") {
|
|
434
|
+
if (!searchFiltersRoles.includes(role))
|
|
435
|
+
throw new RangeError("SearchScreen filtersRole must be narrow or scope");
|
|
436
|
+
if (twoStep && phase !== "results")
|
|
437
|
+
return false;
|
|
438
|
+
return cause !== "query" || role === "scope";
|
|
439
|
+
}
|
|
329
440
|
/**
|
|
330
441
|
* Index to focus after removing item `removedIndex` from a list that now has `remaining` items:
|
|
331
442
|
* the item that slid into its place, else the new last item, else -1 (move to the fallback target).
|
|
@@ -372,7 +483,24 @@ export const notificationRowRecipe = {
|
|
|
372
483
|
thumbnailSize: 44, unreadSize: 6, previewLines: 2, paddingBlock: 12, paddingInline: 16,
|
|
373
484
|
};
|
|
374
485
|
/** Shared profile identity geometry; product owns the avatar content. */
|
|
375
|
-
export const profileSummaryRecipe = Object.freeze({
|
|
376
|
-
|
|
377
|
-
|
|
486
|
+
export const profileSummaryRecipe = Object.freeze({
|
|
487
|
+
avatarSize: 72,
|
|
488
|
+
/**
|
|
489
|
+
* ProfileScreen `editPlacement="summary"` draws the edit action first in the summary's action row. A framed small
|
|
490
|
+
* button reads as the card's own action; the below-summary placement keeps its ghost button (2026-10-15 utilverse
|
|
491
|
+
* account decision: the edit action belongs inside the profile card).
|
|
492
|
+
*/
|
|
493
|
+
editAction: Object.freeze({ tone: "secondary", size: "small" }),
|
|
494
|
+
});
|
|
495
|
+
export const iconTileRecipe = Object.freeze({ iconSize: 64, labelMaxWidth: 72, multilineLabelMaxWidth: 84, multilineLabelMinHeight: 44, ghostOpacity: 0.55, gap: 6, iconRadius: "22%",
|
|
496
|
+
/** Corner radius as a fraction of the icon size; `squircle` keeps the original 22%. */
|
|
497
|
+
shapes: Object.freeze({ squircle: 0.22, square: 0, circle: 0.5 }),
|
|
498
|
+
fillMinSize: 48, fillMaxSize: 68,
|
|
499
|
+
defaults: Object.freeze({ frame: "surface", shape: "squircle", size: "fixed" }) });
|
|
500
|
+
/** The edge a `size="fill"` icon takes in a cell of `cellWidth`, for layout estimates made before the tile measures. */
|
|
501
|
+
export function resolveIconTileFillEdge(cellWidth) {
|
|
502
|
+
if (!Number.isFinite(cellWidth) || cellWidth < 0)
|
|
503
|
+
throw new RangeError("IconTile cell width must be a non-negative finite number");
|
|
504
|
+
return Math.min(iconTileRecipe.fillMaxSize, Math.max(iconTileRecipe.fillMinSize, cellWidth));
|
|
505
|
+
}
|
|
378
506
|
//# sourceMappingURL=screen-patterns.js.map
|