@hjmds/design-contracts 1.27.1 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activity-chip.d.ts +1 -1
- package/dist/activity-chip.js +1 -1
- package/dist/activity-chip.js.map +1 -1
- package/dist/auth-screen.d.ts +71 -5
- package/dist/auth-screen.d.ts.map +1 -1
- package/dist/auth-screen.js +60 -2
- package/dist/auth-screen.js.map +1 -1
- package/dist/base-recipes.d.ts +9 -1
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +7 -1
- package/dist/base-recipes.js.map +1 -1
- package/dist/behaviors.d.ts +1 -1
- package/dist/breadcrumb.d.ts +1 -1
- package/dist/breadcrumb.js +1 -1
- package/dist/breadcrumb.js.map +1 -1
- package/dist/calendar.d.ts +1 -1
- package/dist/calendar.js +2 -2
- package/dist/calendar.js.map +1 -1
- package/dist/catalog.d.ts +148 -89
- package/dist/catalog.d.ts.map +1 -1
- package/dist/component-contracts.d.ts +1 -1
- package/dist/component-contracts.js +1 -1
- package/dist/component-contracts.js.map +1 -1
- package/dist/component-recipes.d.ts +105 -60
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +138 -69
- package/dist/component-recipes.js.map +1 -1
- package/dist/conversation-row.d.ts +2 -2
- package/dist/conversation-row.d.ts.map +1 -1
- package/dist/conversation-row.js +6 -4
- package/dist/conversation-row.js.map +1 -1
- package/dist/counter-badge-recipe.d.ts +1 -1
- package/dist/counter-badge-recipe.js +1 -1
- package/dist/counter-badge-recipe.js.map +1 -1
- package/dist/data-table.d.ts +4 -4
- package/dist/data-table.js +4 -4
- package/dist/data-table.js.map +1 -1
- package/dist/date-picker.d.ts +1 -1
- package/dist/design-profile.d.ts.map +1 -1
- package/dist/design-profile.js +19 -16
- package/dist/design-profile.js.map +1 -1
- package/dist/file-picker.d.ts +1 -1
- package/dist/file-picker.js +1 -1
- package/dist/file-picker.js.map +1 -1
- package/dist/foundations.d.ts +18 -14
- package/dist/foundations.d.ts.map +1 -1
- package/dist/foundations.js +18 -13
- package/dist/foundations.js.map +1 -1
- package/dist/heading.d.ts +13 -13
- package/dist/heading.js.map +1 -1
- package/dist/icon-button-recipe.d.ts +23 -0
- package/dist/icon-button-recipe.d.ts.map +1 -1
- package/dist/icon-button-recipe.js +20 -1
- package/dist/icon-button-recipe.js.map +1 -1
- package/dist/number-field.d.ts +1 -1
- package/dist/otp-field.d.ts +1 -1
- package/dist/password-field.d.ts +1 -1
- package/dist/result.d.ts +1 -1
- package/dist/result.js +2 -2
- package/dist/result.js.map +1 -1
- package/dist/screen-patterns.d.ts +12 -1
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +12 -1
- package/dist/screen-patterns.js.map +1 -1
- package/dist/semantic-colors.d.ts +10 -0
- package/dist/semantic-colors.d.ts.map +1 -1
- package/dist/semantic-colors.js +6 -0
- package/dist/semantic-colors.js.map +1 -1
- package/dist/sheet.d.ts +7 -4
- package/dist/sheet.d.ts.map +1 -1
- package/dist/sheet.js +8 -5
- package/dist/sheet.js.map +1 -1
- package/dist/side-panel.d.ts +1 -1
- package/dist/side-panel.js +2 -2
- package/dist/side-panel.js.map +1 -1
- package/dist/steps.d.ts +1 -1
- package/dist/steps.js +1 -1
- package/dist/steps.js.map +1 -1
- package/dist/tag.d.ts +1 -1
- package/dist/tag.js +1 -1
- package/dist/tag.js.map +1 -1
- package/dist/timeline.d.ts +1 -1
- package/dist/timeline.js +1 -1
- package/dist/timeline.js.map +1 -1
- package/dist/top.d.ts +4 -4
- package/dist/top.d.ts.map +1 -1
- package/dist/top.js +3 -1
- package/dist/top.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/activity-chip.md +5 -1
- package/docs/auth-screen.md +2 -0
- package/docs/design-profile.md +14 -10
- package/docs/generated/component-maturity.md +1 -1
- package/docs/generated/renderer-evidence.json +9 -5
- package/docs/generated/renderer-evidence.md +1 -1
- package/docs/generated/showcase-manifest.json +1 -1
- package/docs/heading.md +14 -9
- package/docs/sheet.md +2 -0
- package/docs/top.md +4 -3
- package/docs/usage/README.md +1 -1
- package/docs/usage/components/auth-screen-layout.md +26 -9
- package/docs/usage/components/bottom-cta.md +6 -2
- package/docs/usage/components/bottom-navigation.md +5 -1
- package/docs/usage/components/breadcrumb.md +5 -1
- package/docs/usage/components/button.md +2 -0
- package/docs/usage/components/chat-message.md +6 -2
- package/docs/usage/components/checkbox-group.md +5 -1
- package/docs/usage/components/collapsible.md +1 -0
- package/docs/usage/components/comment-thread-row.md +1 -1
- package/docs/usage/components/effect-surface.md +2 -2
- package/docs/usage/components/heading.md +14 -12
- package/docs/usage/components/icon-button.md +5 -0
- package/docs/usage/components/inline-edit.md +1 -1
- package/docs/usage/components/list-detail-screen.md +1 -1
- package/docs/usage/components/list-row.md +23 -11
- package/docs/usage/components/list.md +10 -5
- package/docs/usage/components/notice.md +5 -1
- package/docs/usage/components/notification-item.md +5 -1
- package/docs/usage/components/photo-source-sheet.md +1 -1
- package/docs/usage/components/repeatable-field-group.md +1 -1
- package/docs/usage/components/saved-items-screen.md +1 -1
- package/docs/usage/components/screen-layout.md +10 -6
- package/docs/usage/components/search-screen.md +2 -2
- package/docs/usage/components/section.md +5 -1
- package/docs/usage/components/segmented-control.md +1 -1
- package/docs/usage/components/sheet.md +17 -13
- package/docs/usage/components/skeleton.md +41 -15
- package/docs/usage/components/statistic.md +5 -1
- package/docs/usage/components/switch.md +3 -2
- package/docs/usage/components/table-column-settings.md +1 -1
- package/docs/usage/components/tag.md +5 -1
- package/docs/usage/components/text.md +6 -4
- package/docs/usage/components/timeline.md +5 -1
- package/docs/usage/components/toast.md +10 -1
- package/docs/usage/components/top-bar.md +5 -1
- package/docs/usage/components/top.md +1 -1
- package/docs/usage/components/upload-item.md +1 -1
- package/docs/usage/compositions/adaptive-content.md +5 -1
- package/docs/usage/compositions/compound-controls.md +2 -1
- package/docs/usage/compositions/design-profile-comparison.md +1 -1
- package/docs/usage/compositions/family-drawer.md +5 -3
- package/docs/usage/compositions/input-sheet.md +1 -1
- package/docs/usage/compositions/interaction-flow-apply.md +3 -3
- package/docs/usage/compositions/photo-source.md +3 -3
- package/docs/usage/compositions/selection-motion.md +5 -1
- package/docs/usage/compositions/toast-layout.md +4 -4
- package/docs/usage/compositions/visual-foundations.md +5 -4
- package/docs/usage/screens/common-login.md +9 -7
- package/docs/usage/screens/common-search.md +1 -1
- package/docs/usage/screens/landing.md +15 -14
- package/docs/usage/screens/notification-settings.md +4 -2
- package/docs/usage/tokens/typography.md +21 -13
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { collectionItemContract, fieldFrameContract, floatingSurfaceContract, focusIndicatorContract, formSupportContract, } from "./component-contracts.js";
|
|
2
|
-
import { backdrop, breakpoint, control, glyph, layer, largeTextThreshold, layout, fontWeight, motion, motionPreset, opacity, radius, spacing, stroke, } from "./foundations.js";
|
|
2
|
+
import { backdrop, breakpoint, control, glyph, layer, largeTextThreshold, layout, fontWeight, motion, motionPreset, opacity, radius, spacing, stroke, typography, } from "./foundations.js";
|
|
3
3
|
import { semanticColors } from "./semantic-colors.js";
|
|
4
4
|
import { tooltipDescriptorDefaults } from "./tooltip.js";
|
|
5
5
|
import { bottomNavigationRecipeDefaults } from "./bottom-navigation-defaults.js";
|
|
6
|
-
export { iconButtonRecipe, resolveIconButtonPresentation, } from "./icon-button-recipe.js";
|
|
6
|
+
export { iconButtonEdges, iconButtonRecipe, resolveIconButtonEdgeOverhang, resolveIconButtonPresentation, } from "./icon-button-recipe.js";
|
|
7
7
|
export { counterBadgeDefaults, counterBadgeRecipe, formatCounterBadgeCount, } from "./counter-badge-recipe.js";
|
|
8
8
|
export { progressRecipe, } from "./progress-recipe.js";
|
|
9
9
|
export const textRecipe = {
|
|
@@ -19,14 +19,18 @@ export const textRecipe = {
|
|
|
19
19
|
danger: semanticColors.content.danger,
|
|
20
20
|
inverse: semanticColors.content.inverse,
|
|
21
21
|
},
|
|
22
|
+
// 2026-10-17 user decisions: the strongest inline emphasis matches the title weight. strong=700 made unread
|
|
23
|
+
// notification titles and labels heavier than every title after titles moved to 600 ("왜 폰트가 진해"), so
|
|
24
|
+
// strong became 600; the second round ("굵기 좀더 줄여") moved titles 600→500 and display headings 700→600,
|
|
25
|
+
// so strong follows the title role (500) and equals `medium`. Semibold 600 is the key-number/selected step.
|
|
22
26
|
emphasis: {
|
|
23
27
|
regular: fontWeight.regular,
|
|
24
|
-
medium: fontWeight.
|
|
25
|
-
strong: fontWeight
|
|
28
|
+
medium: fontWeight.medium,
|
|
29
|
+
strong: typography.title.fontWeight,
|
|
26
30
|
},
|
|
27
31
|
/**
|
|
28
32
|
* Variants whose omitted `emphasis` renders the variant's typography weight even without a design profile.
|
|
29
|
-
* Label's 500
|
|
33
|
+
* Label's 500 equals the `medium` emphasis step, but the shared `regular` default drew form labels at
|
|
30
34
|
* body weight (2026-10-17 user decision: label 500 on Web and Native). Other variants keep `regular`.
|
|
31
35
|
*/
|
|
32
36
|
variantWeight: ["label"],
|
|
@@ -86,7 +90,7 @@ export const linkRecipe = {
|
|
|
86
90
|
variants: {
|
|
87
91
|
inline: {
|
|
88
92
|
textVariant: "body",
|
|
89
|
-
fontWeight: fontWeight.
|
|
93
|
+
fontWeight: fontWeight.medium,
|
|
90
94
|
underline: "always",
|
|
91
95
|
minHeight: null,
|
|
92
96
|
minWidth: null,
|
|
@@ -94,7 +98,7 @@ export const linkRecipe = {
|
|
|
94
98
|
/** 그 자체가 동작이므로 두 축 모두 최소 터치 타깃. 높이만 잡으면 짧은 라벨이 좁아진다. */
|
|
95
99
|
standalone: {
|
|
96
100
|
textVariant: "body",
|
|
97
|
-
fontWeight: fontWeight.
|
|
101
|
+
fontWeight: fontWeight.medium,
|
|
98
102
|
underline: "hover",
|
|
99
103
|
minHeight: control.minTouchTarget,
|
|
100
104
|
minWidth: control.minTouchTarget,
|
|
@@ -218,7 +222,7 @@ export const badgeRecipe = {
|
|
|
218
222
|
},
|
|
219
223
|
radius: "full",
|
|
220
224
|
borderWidth: stroke.default,
|
|
221
|
-
fontWeight: fontWeight.
|
|
225
|
+
fontWeight: fontWeight.medium,
|
|
222
226
|
};
|
|
223
227
|
/** Search is a field specialization with stable affordance and clear targets. */
|
|
224
228
|
export const searchFieldRecipe = {
|
|
@@ -320,7 +324,8 @@ export const chipRecipe = {
|
|
|
320
324
|
},
|
|
321
325
|
radius: "full",
|
|
322
326
|
borderWidth: stroke.default,
|
|
323
|
-
|
|
327
|
+
// Selected keeps one step above idle (500 → 600) so the state still carries a weight cue on the lighter scale.
|
|
328
|
+
label: { fontWeight: fontWeight.medium, selectedFontWeight: fontWeight.semibold },
|
|
324
329
|
selectionIndicator: {
|
|
325
330
|
color: semanticColors.content.brand,
|
|
326
331
|
glyph: "xs",
|
|
@@ -338,16 +343,36 @@ export const dividerRecipe = {
|
|
|
338
343
|
both: spacing.md,
|
|
339
344
|
},
|
|
340
345
|
};
|
|
346
|
+
/**
|
|
347
|
+
* How far a ListRow surface (pressed, hover, selected, focus ring) reaches past the row text on each inline side.
|
|
348
|
+
* 2026-10-17 user decision: the row text sits on its container's content edge (the 16 screen gutter on a default
|
|
349
|
+
* screen) and only the surface bleeds outward by this amount. Rows used to put it inside as padding, so row text
|
|
350
|
+
* started at 16 + 8 = 24 while section titles and result headers started at 16. Containers that clip (grouped List,
|
|
351
|
+
* VirtualList) keep exactly this much inline room so the bleed is never cut.
|
|
352
|
+
*/
|
|
353
|
+
const listRowSurfaceBleed = spacing.xs;
|
|
354
|
+
/** ListRow leading frame and the gap before its text column; the indented separator reuses both. */
|
|
355
|
+
const listRowLeadingSize = 40;
|
|
356
|
+
const listRowGap = spacing.sm;
|
|
341
357
|
export const listRecipe = {
|
|
342
358
|
slots: ["root", "item", "separator"],
|
|
343
359
|
defaults: { separator: "indented" },
|
|
360
|
+
/**
|
|
361
|
+
* Separators are measured from the list's content edge, where the row text sits, and each one belongs to the row
|
|
362
|
+
* above it (iOS cell separator). "full" spans the row surface (content edge ± bleed). "indented" follows that row's
|
|
363
|
+
* text column: it starts at the text — `leadingInsetStart` (leading 40 + gap 12) when the row has a leading
|
|
364
|
+
* element, `insetStart` 0 when it has none — and ends on the row's trailing content edge (0).
|
|
365
|
+
* 2026-10-17 user report: a fixed 52 start cut the line off midway under rows without leading (text at 16, line
|
|
366
|
+
* from 68), and the −8 end ran past the trailing content.
|
|
367
|
+
*/
|
|
344
368
|
separators: {
|
|
345
369
|
none: null,
|
|
346
|
-
full: { insetStart:
|
|
347
|
-
|
|
348
|
-
// padding the line began 8px before the text (2026-10-17 review E5).
|
|
349
|
-
indented: { insetStart: spacing.xs + 40 + spacing.sm, insetEnd: 0 },
|
|
370
|
+
full: { insetStart: -listRowSurfaceBleed, leadingInsetStart: -listRowSurfaceBleed, insetEnd: -listRowSurfaceBleed },
|
|
371
|
+
indented: { insetStart: 0, leadingInsetStart: listRowLeadingSize + listRowGap, insetEnd: 0 },
|
|
350
372
|
},
|
|
373
|
+
// A grouped list paints its own rounded surface and clips to it, so it leaves the row bleed as inline padding:
|
|
374
|
+
// rows keep their full surface inside the radius and the text stays 8 inside the group edge as before.
|
|
375
|
+
grouped: { paddingInline: listRowSurfaceBleed },
|
|
351
376
|
background: null,
|
|
352
377
|
};
|
|
353
378
|
export const listRowRecipe = {
|
|
@@ -359,13 +384,13 @@ export const listRowRecipe = {
|
|
|
359
384
|
compact: {
|
|
360
385
|
oneLineMinHeight: control.minTouchTarget,
|
|
361
386
|
twoLineMinHeight: 60,
|
|
362
|
-
paddingHorizontal:
|
|
387
|
+
paddingHorizontal: listRowSurfaceBleed,
|
|
363
388
|
paddingVertical: spacing.xxs,
|
|
364
389
|
},
|
|
365
390
|
comfortable: {
|
|
366
391
|
oneLineMinHeight: layout.rowHeight.singleLine,
|
|
367
392
|
twoLineMinHeight: layout.rowHeight.twoLine,
|
|
368
|
-
paddingHorizontal:
|
|
393
|
+
paddingHorizontal: listRowSurfaceBleed,
|
|
369
394
|
paddingVertical: spacing.xs,
|
|
370
395
|
},
|
|
371
396
|
/*
|
|
@@ -377,27 +402,33 @@ export const listRowRecipe = {
|
|
|
377
402
|
relaxed: {
|
|
378
403
|
oneLineMinHeight: layout.rowHeight.singleLine + spacing.xs,
|
|
379
404
|
twoLineMinHeight: layout.rowHeight.twoLine + spacing.xs,
|
|
380
|
-
paddingHorizontal:
|
|
405
|
+
paddingHorizontal: listRowSurfaceBleed,
|
|
381
406
|
paddingVertical: spacing.sm,
|
|
382
407
|
},
|
|
383
408
|
spacious: {
|
|
384
409
|
oneLineMinHeight: layout.rowHeight.singleLine + spacing.md,
|
|
385
410
|
twoLineMinHeight: layout.rowHeight.twoLine + spacing.md,
|
|
386
|
-
paddingHorizontal:
|
|
411
|
+
paddingHorizontal: listRowSurfaceBleed,
|
|
387
412
|
paddingVertical: spacing.md,
|
|
388
413
|
},
|
|
389
414
|
},
|
|
390
415
|
// Loading uses the same row/line boxes as content; only the copy width is illustrative.
|
|
391
416
|
loading: { titleWidthPercent: 60, descriptionWidthPercent: 40, trailingWidth: spacing.xxl },
|
|
392
|
-
|
|
393
|
-
|
|
417
|
+
/**
|
|
418
|
+
* Inline placement (2026-10-17 user decision, see `listRowSurfaceBleed`): the content box sits on the container's
|
|
419
|
+
* content edge and the surface bleeds outward by `paddingHorizontal` — renderers draw the row with that padding and
|
|
420
|
+
* the same negative inline margin, so pressed/selected fills keep 8 of room beyond the text.
|
|
421
|
+
*/
|
|
422
|
+
surfaceBleed: listRowSurfaceBleed,
|
|
423
|
+
gap: listRowGap,
|
|
424
|
+
leadingSize: listRowLeadingSize,
|
|
394
425
|
/**
|
|
395
426
|
* Frame geometry for the leading slot. The size was already declared here but
|
|
396
427
|
* no renderer bound it, so every consumer re-derived the avatar box (radius,
|
|
397
428
|
* clipping, centering) in product styles.
|
|
398
429
|
*/
|
|
399
430
|
leadingShapes: { square: null, circle: "full" },
|
|
400
|
-
title: { color: semanticColors.content.body, textVariant: "bodyLarge", fontWeight: fontWeight
|
|
431
|
+
title: { color: semanticColors.content.body, textVariant: "bodyLarge", fontWeight: typography.title.fontWeight },
|
|
401
432
|
description: { color: semanticColors.content.secondary, textVariant: "body" },
|
|
402
433
|
trailing: {
|
|
403
434
|
textColor: semanticColors.content.secondary,
|
|
@@ -420,7 +451,7 @@ export const accordionRecipe = {
|
|
|
420
451
|
},
|
|
421
452
|
paddingHorizontal: spacing.xs,
|
|
422
453
|
gap: spacing.sm,
|
|
423
|
-
title: { color: semanticColors.content.body, textVariant: "bodyLarge", fontWeight: fontWeight
|
|
454
|
+
title: { color: semanticColors.content.body, textVariant: "bodyLarge", fontWeight: typography.title.fontWeight },
|
|
424
455
|
indicator: { color: semanticColors.content.secondary, glyph: "sm" },
|
|
425
456
|
panel: {
|
|
426
457
|
color: semanticColors.content.body,
|
|
@@ -560,7 +591,7 @@ export const selectRecipe = {
|
|
|
560
591
|
paddingVertical: spacing.xs,
|
|
561
592
|
},
|
|
562
593
|
optionLeading: { color: semanticColors.content.secondary, glyph: "sm" },
|
|
563
|
-
optionLabel: { fontWeight: fontWeight.
|
|
594
|
+
optionLabel: { fontWeight: fontWeight.medium, selectedFontWeight: fontWeight.semibold },
|
|
564
595
|
selectionIndicator: { color: semanticColors.border.focus, glyph: "sm" },
|
|
565
596
|
stateMessage: {
|
|
566
597
|
color: semanticColors.content.secondary,
|
|
@@ -631,6 +662,8 @@ export const comboboxRecipe = {
|
|
|
631
662
|
states: selectRecipe.states,
|
|
632
663
|
transition: selectRecipe.transition,
|
|
633
664
|
};
|
|
665
|
+
/** Block-side inset of the connected track: `container.padding` + `container.borderWidth`. */
|
|
666
|
+
const segmentedTrackInset = spacing.xxs + stroke.subtle;
|
|
634
667
|
export const segmentedControlRecipe = {
|
|
635
668
|
// Text navigation reuses radio selection, without cloning a product-only tab engine.
|
|
636
669
|
text: { gap: spacing.md, indicatorWidth: 16, indicatorHeight: 2, indicatorRadius: radius.full,
|
|
@@ -667,16 +700,25 @@ export const segmentedControlRecipe = {
|
|
|
667
700
|
padding: spacing.xxs,
|
|
668
701
|
gap: spacing.xxs,
|
|
669
702
|
},
|
|
703
|
+
/**
|
|
704
|
+
* `height` is the connected control's outer height (track border + padding included) and matches the Button /
|
|
705
|
+
* IconButton / Chip height of the same size step, so a segmented control and a chip in one row line up.
|
|
706
|
+
* `minHeight` is the option inside the track (height − 2 × (`container.padding` + `container.borderWidth`)) and `hitSlop` extends each
|
|
707
|
+
* option vertically to the 44 touch target. Before 2026-10-17 `minHeight` was the option itself, so the track
|
|
708
|
+
* drew 46/54 next to 36/44 chips and buttons (DiscoveryGallery sort row, user request "높이 맞춰").
|
|
709
|
+
*/
|
|
670
710
|
sizes: {
|
|
671
711
|
small: {
|
|
672
|
-
|
|
673
|
-
|
|
712
|
+
height: control.buttonHeight.small,
|
|
713
|
+
minHeight: control.buttonHeight.small - 2 * segmentedTrackInset,
|
|
714
|
+
hitSlop: (control.minTouchTarget - (control.buttonHeight.small - 2 * segmentedTrackInset)) / 2,
|
|
674
715
|
paddingHorizontal: spacing.sm,
|
|
675
716
|
textVariant: "label",
|
|
676
717
|
},
|
|
677
718
|
medium: {
|
|
678
|
-
|
|
679
|
-
|
|
719
|
+
height: control.buttonHeight.medium,
|
|
720
|
+
minHeight: control.buttonHeight.medium - 2 * segmentedTrackInset,
|
|
721
|
+
hitSlop: (control.minTouchTarget - (control.buttonHeight.medium - 2 * segmentedTrackInset)) / 2,
|
|
680
722
|
paddingHorizontal: spacing.md,
|
|
681
723
|
textVariant: "body",
|
|
682
724
|
},
|
|
@@ -685,7 +727,7 @@ export const segmentedControlRecipe = {
|
|
|
685
727
|
radius: "md",
|
|
686
728
|
gap: spacing.xxs,
|
|
687
729
|
idleContent: semanticColors.content.secondary,
|
|
688
|
-
fontWeight: fontWeight.
|
|
730
|
+
fontWeight: fontWeight.medium,
|
|
689
731
|
selectedBackground: semanticColors.canvas,
|
|
690
732
|
selectedContent: semanticColors.content.brand,
|
|
691
733
|
/**
|
|
@@ -695,7 +737,7 @@ export const segmentedControlRecipe = {
|
|
|
695
737
|
*/
|
|
696
738
|
selectedBorder: semanticColors.border.focus,
|
|
697
739
|
selectedBorderWidth: stroke.strong,
|
|
698
|
-
selectedFontWeight: fontWeight.
|
|
740
|
+
selectedFontWeight: fontWeight.semibold,
|
|
699
741
|
focusRing: semanticColors.border.focus,
|
|
700
742
|
pressedOpacity: opacity.pressed,
|
|
701
743
|
disabledOpacity: opacity.disabled,
|
|
@@ -756,11 +798,16 @@ export const switchRecipe = {
|
|
|
756
798
|
pressedOpacity: opacity.pressed,
|
|
757
799
|
},
|
|
758
800
|
rowMinHeight: control.minTouchTarget,
|
|
759
|
-
// A switch
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
//
|
|
763
|
-
|
|
801
|
+
// A described switch is a two-line row: the copy (body 20 + gap 4 + caption 16 = 40) with the
|
|
802
|
+
// comfortable list-row vertical padding (8) on both sides. The padding is drawn by the row itself
|
|
803
|
+
// so it survives larger text, and the minimum keeps neighbouring rows equal (a product author found
|
|
804
|
+
// content-only height gave them different heights). It used to borrow ListRow's 68, which is sized
|
|
805
|
+
// for a bodyLarge title + body description; on the smaller switch copy that left 14 above and below,
|
|
806
|
+
// and stacked rows read as widely spaced (2026-10-17 user report, notification settings: 84 pitch
|
|
807
|
+
// for 40 of copy, together with a Stack gap the rows must not have).
|
|
808
|
+
rowPaddingVertical: listRowRecipe.density.comfortable.paddingVertical,
|
|
809
|
+
rowTwoLineMinHeight: typography.body.lineHeight + spacing.xxs + typography.caption.lineHeight
|
|
810
|
+
+ 2 * listRowRecipe.density.comfortable.paddingVertical,
|
|
764
811
|
};
|
|
765
812
|
export const selectionControlRecipe = {
|
|
766
813
|
slots: [
|
|
@@ -836,8 +883,8 @@ export const selectionControlRecipe = {
|
|
|
836
883
|
},
|
|
837
884
|
label: {
|
|
838
885
|
color: semanticColors.content.body,
|
|
839
|
-
fontWeight: fontWeight.
|
|
840
|
-
checkedFontWeight: fontWeight.
|
|
886
|
+
fontWeight: fontWeight.medium,
|
|
887
|
+
checkedFontWeight: fontWeight.semibold,
|
|
841
888
|
},
|
|
842
889
|
leading: { size: "md", color: semanticColors.content.secondary },
|
|
843
890
|
description: { color: semanticColors.content.secondary },
|
|
@@ -1001,8 +1048,8 @@ export const bottomNavigationRecipe = {
|
|
|
1001
1048
|
},
|
|
1002
1049
|
},
|
|
1003
1050
|
label: {
|
|
1004
|
-
fontWeight: fontWeight.
|
|
1005
|
-
selectedFontWeight: fontWeight.
|
|
1051
|
+
fontWeight: fontWeight.medium,
|
|
1052
|
+
selectedFontWeight: fontWeight.semibold,
|
|
1006
1053
|
textAlign: "center",
|
|
1007
1054
|
wrap: true,
|
|
1008
1055
|
fixedLines: null,
|
|
@@ -1073,7 +1120,7 @@ export const tabsRecipe = {
|
|
|
1073
1120
|
},
|
|
1074
1121
|
gap: spacing.xs,
|
|
1075
1122
|
icon: { glyph: "xs" },
|
|
1076
|
-
label: { fontWeight: fontWeight.
|
|
1123
|
+
label: { fontWeight: fontWeight.medium, selectedFontWeight: fontWeight.semibold },
|
|
1077
1124
|
layouts: {
|
|
1078
1125
|
content: { fitted: false },
|
|
1079
1126
|
fitted: { fitted: true },
|
|
@@ -1112,21 +1159,28 @@ export const noticeRecipe = {
|
|
|
1112
1159
|
contentGap: spacing.xxs,
|
|
1113
1160
|
iconSize: "sm",
|
|
1114
1161
|
borderWidth: stroke.default,
|
|
1115
|
-
title: { textVariant: "body", fontWeight: fontWeight
|
|
1162
|
+
title: { textVariant: "body", fontWeight: typography.title.fontWeight },
|
|
1116
1163
|
description: { color: semanticColors.content.body, textVariant: "body" },
|
|
1117
1164
|
};
|
|
1118
1165
|
export const skeletonRecipe = {
|
|
1119
|
-
slots: ["root"],
|
|
1166
|
+
slots: ["root", "line"],
|
|
1120
1167
|
// animated는 0.9.12까지 false였다. 정지한 블록은 로딩이 아니라 깨진 화면으로
|
|
1121
1168
|
// 읽힌다. 전환 근거와 소비 측 migration은
|
|
1122
1169
|
// .changeset/skeleton-pulse-by-default.md에 있다.
|
|
1123
|
-
defaults: { shape: "block", animated: true },
|
|
1124
|
-
|
|
1170
|
+
defaults: { shape: "block", animated: true, lines: 1 },
|
|
1171
|
+
// 2026-10-17 사용자 요청: canvas 채움 + 1px 테두리는 흰 배경에서 거의 보이지 않아 로딩 모양을 알 수 없었다.
|
|
1172
|
+
// 2026-09-29 "넓은 회색 면은 canvas" 방향의 예외로, 자리표시는 채운 면이어야 모양이 읽힌다(skeleton.md 근거).
|
|
1173
|
+
background: semanticColors.surface.placeholder,
|
|
1174
|
+
// `aspectRatio`가 있으면 높이 대신 폭에 비례한다(media). `button`은 medium 버튼 높이·모서리와 같다.
|
|
1125
1175
|
shapes: {
|
|
1126
|
-
block: { radius: "md", defaultHeight: spacing.xxl },
|
|
1127
|
-
text: { radius: "sm", defaultHeight: spacing.md },
|
|
1128
|
-
circle: { radius: "full", defaultHeight: glyph.xxl },
|
|
1129
|
-
|
|
1176
|
+
block: { radius: "md", defaultHeight: spacing.xxl, aspectRatio: null },
|
|
1177
|
+
text: { radius: "sm", defaultHeight: spacing.md, aspectRatio: null },
|
|
1178
|
+
circle: { radius: "full", defaultHeight: glyph.xxl, aspectRatio: null },
|
|
1179
|
+
media: { radius: "md", defaultHeight: null, aspectRatio: 16 / 9 },
|
|
1180
|
+
button: { radius: "md", defaultHeight: control.buttonHeight.medium, aspectRatio: null },
|
|
1181
|
+
},
|
|
1182
|
+
// `lines` > 1 (text 전용): 줄 사이 간격과 짧게 끝나는 마지막 줄. 16 막대 + 8 간격 = 본문 큰 글자 줄 높이 24.
|
|
1183
|
+
lines: { gap: spacing.xs, lastLineWidthPercent: 60 },
|
|
1130
1184
|
animation: {
|
|
1131
1185
|
duration: motion.slow * 4,
|
|
1132
1186
|
easing: "standard",
|
|
@@ -1145,7 +1199,7 @@ export const emptyStateRecipe = {
|
|
|
1145
1199
|
paddingHorizontal: spacing.xl,
|
|
1146
1200
|
gap: spacing.xs,
|
|
1147
1201
|
icon: { size: "lg", color: semanticColors.content.decorative },
|
|
1148
|
-
title: { textVariant: "body", color: semanticColors.content.primary, fontWeight: fontWeight
|
|
1202
|
+
title: { textVariant: "body", color: semanticColors.content.primary, fontWeight: typography.title.fontWeight },
|
|
1149
1203
|
description: { textVariant: "label", color: semanticColors.content.secondary },
|
|
1150
1204
|
};
|
|
1151
1205
|
export const spinnerRecipe = {
|
|
@@ -1197,7 +1251,7 @@ export const loadMoreRecipe = {
|
|
|
1197
1251
|
radius: "md",
|
|
1198
1252
|
color: semanticColors.content.brand,
|
|
1199
1253
|
textVariant: "label",
|
|
1200
|
-
fontWeight: fontWeight.
|
|
1254
|
+
fontWeight: fontWeight.medium,
|
|
1201
1255
|
},
|
|
1202
1256
|
spinner: { size: "small", tone: "brand" },
|
|
1203
1257
|
states: {
|
|
@@ -1219,7 +1273,7 @@ export const avatarRecipe = {
|
|
|
1219
1273
|
background: semanticColors.canvas,
|
|
1220
1274
|
content: semanticColors.content.secondary,
|
|
1221
1275
|
border: semanticColors.border.default,
|
|
1222
|
-
/** Initials
|
|
1276
|
+
/** Initials use the strong emphasis step; their size follows the avatar (Web `.hjm-avatar[data-size]` steps). */
|
|
1223
1277
|
initialsWeight: "strong",
|
|
1224
1278
|
};
|
|
1225
1279
|
/**
|
|
@@ -1277,18 +1331,20 @@ export const statisticRecipe = {
|
|
|
1277
1331
|
},
|
|
1278
1332
|
label: {
|
|
1279
1333
|
color: semanticColors.content.secondary,
|
|
1280
|
-
fontWeight: fontWeight.
|
|
1334
|
+
fontWeight: fontWeight.medium,
|
|
1281
1335
|
},
|
|
1282
1336
|
value: {
|
|
1283
1337
|
color: semanticColors.content.primary,
|
|
1284
|
-
|
|
1338
|
+
// The key number keeps the semibold emphasis step (600, 2026-10-17 lighter scale) at the `heading` size.
|
|
1339
|
+
// It used to borrow the display heading weight, which was removed with the display tier the same day.
|
|
1340
|
+
fontWeight: fontWeight.semibold,
|
|
1285
1341
|
numericVariant: "tabular",
|
|
1286
1342
|
maxLines: null,
|
|
1287
1343
|
},
|
|
1288
1344
|
affix: {
|
|
1289
1345
|
color: semanticColors.content.body,
|
|
1290
1346
|
textVariant: "body",
|
|
1291
|
-
fontWeight: fontWeight.
|
|
1347
|
+
fontWeight: fontWeight.medium,
|
|
1292
1348
|
},
|
|
1293
1349
|
hint: {
|
|
1294
1350
|
color: semanticColors.content.secondary,
|
|
@@ -1296,7 +1352,7 @@ export const statisticRecipe = {
|
|
|
1296
1352
|
},
|
|
1297
1353
|
trend: {
|
|
1298
1354
|
textVariant: "caption",
|
|
1299
|
-
fontWeight: fontWeight.
|
|
1355
|
+
fontWeight: fontWeight.medium,
|
|
1300
1356
|
gap: spacing.xxs,
|
|
1301
1357
|
marks: {
|
|
1302
1358
|
up: "trendUp",
|
|
@@ -1361,12 +1417,8 @@ export const sheetRecipe = {
|
|
|
1361
1417
|
showWhen: "swipe-dismiss-enabled",
|
|
1362
1418
|
},
|
|
1363
1419
|
header: { minHeight: control.minTouchTarget, gap: spacing.sm },
|
|
1364
|
-
title:
|
|
1365
|
-
|
|
1366
|
-
textVariant: "title",
|
|
1367
|
-
fontWeight: fontWeight.bold,
|
|
1368
|
-
gap: spacing.xxs,
|
|
1369
|
-
},
|
|
1420
|
+
// No visual `title` region: sheets render the title only as the accessible name (2026-10-17 user decision).
|
|
1421
|
+
// The "title" slot stays because the title is still the dialog's name.
|
|
1370
1422
|
body: {
|
|
1371
1423
|
color: semanticColors.content.body,
|
|
1372
1424
|
textVariant: "body",
|
|
@@ -1450,7 +1502,7 @@ export const alertDialogRecipe = {
|
|
|
1450
1502
|
title: {
|
|
1451
1503
|
color: semanticColors.content.primary,
|
|
1452
1504
|
textVariant: "title",
|
|
1453
|
-
fontWeight: fontWeight
|
|
1505
|
+
fontWeight: typography.title.fontWeight,
|
|
1454
1506
|
},
|
|
1455
1507
|
description: {
|
|
1456
1508
|
color: semanticColors.content.secondary,
|
|
@@ -1467,6 +1519,12 @@ export const alertDialogRecipe = {
|
|
|
1467
1519
|
},
|
|
1468
1520
|
transition: dialogRecipe.transition,
|
|
1469
1521
|
};
|
|
1522
|
+
// First-line anchor (2026-10-17 user: "기본 토스트 좌우랑 글자 라인이 안맞는데"). The badge box is the one
|
|
1523
|
+
// line every leading column, first text line and close glyph centre on; the close keeps its 44 touch target but
|
|
1524
|
+
// overhangs so its glyph, not its hit area, sets the visible inset. Derived here so both renderers read numbers.
|
|
1525
|
+
const toastBadgeDiameter = 32;
|
|
1526
|
+
const toastCloseDiameter = control.minTouchTarget;
|
|
1527
|
+
const toastCloseGlyph = "xs";
|
|
1470
1528
|
export const toastRecipe = {
|
|
1471
1529
|
slots: [
|
|
1472
1530
|
"viewport",
|
|
@@ -1542,13 +1600,20 @@ export const toastRecipe = {
|
|
|
1542
1600
|
radius: "xl",
|
|
1543
1601
|
shadow: floatingSurfaceContract.shadow,
|
|
1544
1602
|
minHeight: layout.rowHeight.singleLine,
|
|
1545
|
-
|
|
1603
|
+
// block sm + anchor 32 + sm = the 56 single-line row; inline md on both edges so the badge's start inset
|
|
1604
|
+
// equals the close glyph's end inset (Web had sm/xs/sm/md, Native md on all sides — 2026-10-17 measurement).
|
|
1605
|
+
padding: { block: spacing.sm, inline: spacing.md },
|
|
1546
1606
|
gap: spacing.sm,
|
|
1547
1607
|
maxWidth: 420,
|
|
1548
1608
|
},
|
|
1609
|
+
// First text line (title, else description) is centred on this box: content insets its first line by
|
|
1610
|
+
// (blockSize − first-line line-height) / 2. Multi-line copy grows downward; the lead and close stay beside
|
|
1611
|
+
// the first line instead of centring on the whole block. Product media (44) start-aligns to the same top and
|
|
1612
|
+
// is not the anchor: centring the title on the media pushed the description below the avatar.
|
|
1613
|
+
anchor: { blockSize: toastBadgeDiameter },
|
|
1549
1614
|
// Retired in 1.10.0 (width 0): the icon badge carries tone. Kept so the slot contract stays additive.
|
|
1550
1615
|
toneMark: { width: 0, radius: "full" },
|
|
1551
|
-
icon: { glyph: "sm", badgeDiameter:
|
|
1616
|
+
icon: { glyph: "sm", badgeDiameter: toastBadgeDiameter, badgeRadius: "full" },
|
|
1552
1617
|
// A product media (mascot, avatar) replaces the tone badge in the same leading column. 44 keeps a
|
|
1553
1618
|
// character legible; it is decorative, so the tone still lives in `data-tone` and the copy.
|
|
1554
1619
|
media: { size: 44 },
|
|
@@ -1556,7 +1621,7 @@ export const toastRecipe = {
|
|
|
1556
1621
|
title: {
|
|
1557
1622
|
color: semanticColors.content.primary,
|
|
1558
1623
|
textVariant: "body",
|
|
1559
|
-
fontWeight: fontWeight
|
|
1624
|
+
fontWeight: typography.title.fontWeight,
|
|
1560
1625
|
},
|
|
1561
1626
|
// Secondary copy so the title leads; body-on-body read as one grey block.
|
|
1562
1627
|
description: {
|
|
@@ -1564,7 +1629,7 @@ export const toastRecipe = {
|
|
|
1564
1629
|
textVariant: "body",
|
|
1565
1630
|
},
|
|
1566
1631
|
// Step dots of a running task: done and current are brand, upcoming is the divider colour; the current
|
|
1567
|
-
// label is
|
|
1632
|
+
// label is emphasized. Labels sit beside each dot so the row reads without colour alone (WCAG 1.4.1).
|
|
1568
1633
|
progress: {
|
|
1569
1634
|
gap: spacing.sm,
|
|
1570
1635
|
dotSize: 6,
|
|
@@ -1584,14 +1649,18 @@ export const toastRecipe = {
|
|
|
1584
1649
|
radius: "full",
|
|
1585
1650
|
align: "start",
|
|
1586
1651
|
textVariant: "body",
|
|
1587
|
-
fontWeight: fontWeight.
|
|
1652
|
+
fontWeight: fontWeight.medium,
|
|
1588
1653
|
minHeight: control.minTouchTarget,
|
|
1589
1654
|
paddingHorizontal: spacing.md,
|
|
1590
1655
|
},
|
|
1591
1656
|
close: {
|
|
1592
1657
|
color: semanticColors.content.secondary,
|
|
1593
|
-
diameter:
|
|
1594
|
-
glyph:
|
|
1658
|
+
diameter: toastCloseDiameter,
|
|
1659
|
+
glyph: toastCloseGlyph,
|
|
1660
|
+
// Negative block margin on both sides: the 44 target centres on the anchor line without stretching the row.
|
|
1661
|
+
blockOverhang: (toastCloseDiameter - toastBadgeDiameter) / 2,
|
|
1662
|
+
// Negative inline-end margin: the glyph box ends at the md padding edge, mirroring the badge's start inset.
|
|
1663
|
+
inlineOverhang: (toastCloseDiameter - glyph[toastCloseGlyph]) / 2,
|
|
1595
1664
|
},
|
|
1596
1665
|
states: {
|
|
1597
1666
|
pressedOpacity: opacity.pressed,
|
|
@@ -1649,7 +1718,7 @@ export const topBarRecipe = {
|
|
|
1649
1718
|
paddingHorizontal: spacing.md,
|
|
1650
1719
|
gap: spacing.xs,
|
|
1651
1720
|
largeTextThreshold,
|
|
1652
|
-
title: { textVariant: "bodyLarge", color: semanticColors.content.primary, fontWeight: fontWeight
|
|
1721
|
+
title: { textVariant: "bodyLarge", color: semanticColors.content.primary, fontWeight: typography.title.fontWeight },
|
|
1653
1722
|
titleAction: {
|
|
1654
1723
|
minHeight: control.minTouchTarget,
|
|
1655
1724
|
minWidth: control.minTouchTarget,
|
|
@@ -1710,7 +1779,7 @@ export const sectionRecipe = {
|
|
|
1710
1779
|
gap: spacing.xs,
|
|
1711
1780
|
headerGap: spacing.sm,
|
|
1712
1781
|
copyGap: spacing.xxs,
|
|
1713
|
-
title: { textVariant: "title", color: semanticColors.content.body, fontWeight: fontWeight
|
|
1782
|
+
title: { textVariant: "title", color: semanticColors.content.body, fontWeight: typography.title.fontWeight },
|
|
1714
1783
|
description: { textVariant: "caption", color: semanticColors.content.secondary },
|
|
1715
1784
|
};
|
|
1716
1785
|
//# sourceMappingURL=component-recipes.js.map
|