@multiplatform.one/forms 7.54.0 → 7.55.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.
Files changed (42) hide show
  1. package/dist/cjs/Button/index.cjs +10 -1
  2. package/dist/cjs/Button/index.native.js +13 -4
  3. package/dist/cjs/Button/index.native.js.map +1 -1
  4. package/dist/cjs/fields/Progress/index.cjs +7 -2
  5. package/dist/cjs/fields/Progress/index.native.js +7 -2
  6. package/dist/cjs/fields/Progress/index.native.js.map +1 -1
  7. package/dist/cjs/formSpacing.cjs +1 -1
  8. package/dist/cjs/formSpacing.native.js +1 -1
  9. package/dist/esm/Button/index.mjs +11 -2
  10. package/dist/esm/Button/index.mjs.map +1 -1
  11. package/dist/esm/Button/index.native.js +14 -5
  12. package/dist/esm/Button/index.native.js.map +1 -1
  13. package/dist/esm/fields/Progress/index.mjs +8 -3
  14. package/dist/esm/fields/Progress/index.mjs.map +1 -1
  15. package/dist/esm/fields/Progress/index.native.js +8 -3
  16. package/dist/esm/fields/Progress/index.native.js.map +1 -1
  17. package/dist/esm/formSpacing.mjs +1 -1
  18. package/dist/esm/formSpacing.native.js +1 -1
  19. package/dist/jsx/Button/index.mjs +11 -2
  20. package/dist/jsx/Button/index.mjs.map +1 -1
  21. package/dist/jsx/Button/index.native.js +13 -4
  22. package/dist/jsx/Button/index.native.js.map +1 -1
  23. package/dist/jsx/fields/Progress/index.mjs +8 -3
  24. package/dist/jsx/fields/Progress/index.mjs.map +1 -1
  25. package/dist/jsx/fields/Progress/index.native.js +7 -2
  26. package/dist/jsx/fields/Progress/index.native.js.map +1 -1
  27. package/dist/jsx/formSpacing.mjs +1 -1
  28. package/dist/jsx/formSpacing.native.js +1 -1
  29. package/package.json +8 -8
  30. package/src/Button/Button.accentInk.spec.tsx +135 -0
  31. package/src/Button/index.tsx +14 -0
  32. package/src/FieldGroup.spec.tsx +4 -4
  33. package/src/Form/form.spec.tsx +4 -4
  34. package/src/FormGrid.spec.tsx +15 -14
  35. package/src/fieldLayout.spec.tsx +7 -7
  36. package/src/fields/Progress/Progress.spec.tsx +21 -0
  37. package/src/fields/Progress/index.tsx +18 -3
  38. package/src/formSpacing.ts +3 -3
  39. package/types/Button/index.d.ts +8 -1
  40. package/types/Button/index.d.ts.map +1 -1
  41. package/types/fields/Progress/index.d.ts.map +1 -1
  42. package/types/formSpacing.d.ts +3 -3
@@ -29,7 +29,7 @@ __export(formSpacing_exports, {
29
29
  formSectionGap: () => formSectionGap
30
30
  });
31
31
  module.exports = __toCommonJS(formSpacing_exports);
32
- var formFieldGap = "$3";
32
+ var formFieldGap = "$4";
33
33
  var formSectionGap = "$8";
34
34
  var formReadableMaxWidth = 560;
35
35
  //# sourceMappingURL=formSpacing.native.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/forms",
3
- "version": "7.54.0",
3
+ "version": "7.55.0",
4
4
  "description": "Cross-platform form components with URL state management",
5
5
  "keywords": [
6
6
  "cross-platform",
@@ -57,10 +57,10 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "@floating-ui/react": "^0.27.20",
60
- "@multiplatform.one/platform": "^7.54.0",
61
- "@multiplatform.one/router": "^7.54.0",
62
- "@multiplatform.one/table-primitives": "^7.54.0",
63
- "@multiplatform.one/theme": "^7.54.0",
60
+ "@multiplatform.one/platform": "^7.55.0",
61
+ "@multiplatform.one/router": "^7.55.0",
62
+ "@multiplatform.one/table-primitives": "^7.55.0",
63
+ "@multiplatform.one/theme": "^7.55.0",
64
64
  "@phosphor-icons/react": "^2.1.10",
65
65
  "@tamagui/get-font-sized": "2.7.6",
66
66
  "@tamagui/get-token": "2.7.6",
@@ -79,9 +79,9 @@
79
79
  "tamagui": "2.7.6"
80
80
  },
81
81
  "devDependencies": {
82
- "@multiplatform.one/config": "7.54.0",
83
- "@multiplatform.one/storybook": "7.54.0",
84
- "@multiplatform.one/test-utils": "7.54.0",
82
+ "@multiplatform.one/config": "7.55.0",
83
+ "@multiplatform.one/storybook": "7.55.0",
84
+ "@multiplatform.one/test-utils": "7.55.0",
85
85
  "@tamagui/build": "2.7.6",
86
86
  "@tamagui/config": "2.7.6",
87
87
  "@tamagui/vite-plugin": "2.7.6",
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ *
4
+ * LC-145 (FZ-7993): a chromeless bar button with `accentInk` paints its label
5
+ * and its frame's `color` (currentColor glyphs) in the host's identity ink
6
+ * (`useIdentityInkOnSurface`), the iOS bar-button tint: the hue under a colour
7
+ * theme, the primary ink under the hueless house theme, never the grey accent
8
+ * step. Disabled leaves the tint for its own ink, and without the prop a
9
+ * chromeless button keeps its neutral ink.
10
+ *
11
+ * jsdom cascades no Tamagui CSS, so this reads the atomic `_col-` class and
12
+ * the injected rule behind it.
13
+ */
14
+
15
+ import { cleanup, render, screen } from "@testing-library/react";
16
+ import {
17
+ createDefaultThemeConfig,
18
+ useAccentOnSurface,
19
+ useIdentityInkOnSurface,
20
+ } from "@multiplatform.one/theme";
21
+ import type { ReactElement } from "react";
22
+ import { TamaguiProvider, YStack } from "tamagui";
23
+ import { afterEach, describe, expect, it } from "vitest";
24
+ import { Button } from "./index";
25
+
26
+ afterEach(cleanup);
27
+
28
+ const houseConfig = createDefaultThemeConfig();
29
+ const SCHEMES = ["light", "dark"] as const;
30
+
31
+ function AccentProbe() {
32
+ return (
33
+ <span
34
+ data-testid="accent-probe"
35
+ data-ink={useIdentityInkOnSurface()}
36
+ data-accent={useAccentOnSurface()}
37
+ />
38
+ );
39
+ }
40
+
41
+ function houseRender(ui: ReactElement, scheme: string) {
42
+ return render(
43
+ <TamaguiProvider config={houseConfig.tamagui} defaultTheme={scheme}>
44
+ <YStack backgroundColor="$background">
45
+ <AccentProbe />
46
+ {ui}
47
+ </YStack>
48
+ </TamaguiProvider>,
49
+ );
50
+ }
51
+
52
+ function injectedRuleValue(className: string, property: string): string | null {
53
+ for (const sheet of Array.from(document.styleSheets)) {
54
+ for (const rule of Array.from(sheet.cssRules)) {
55
+ if (!(rule instanceof CSSStyleRule)) continue;
56
+ if (!rule.selectorText.endsWith(`.${className}`)) continue;
57
+ const value = rule.style.getPropertyValue(property);
58
+ if (value) return value.trim();
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+
64
+ function paintedColor(el: Element): string | null {
65
+ const cls = Array.from(el.classList).find(
66
+ (c) => c.startsWith("_col-") && !/^_col-0(hover|active|focus|press)/.test(c),
67
+ );
68
+ return cls ? injectedRuleValue(cls, "color") : null;
69
+ }
70
+
71
+ function accentInk(): string {
72
+ return (screen.getByTestId("accent-probe").getAttribute("data-ink") ?? "").toLowerCase();
73
+ }
74
+
75
+ function greyAccent(): string {
76
+ return (screen.getByTestId("accent-probe").getAttribute("data-accent") ?? "").toLowerCase();
77
+ }
78
+
79
+ describe("Button accentInk (LC-145 bar tint)", () => {
80
+ for (const scheme of SCHEMES) {
81
+ it(`${scheme}: a chromeless accentInk button paints label and frame in the identity ink`, () => {
82
+ houseRender(
83
+ <Button chromeless accentInk>
84
+ Components
85
+ </Button>,
86
+ scheme,
87
+ );
88
+ const label = screen.getByText("Components");
89
+ const frame = label.closest("[role=button]") as Element;
90
+ const ink = accentInk();
91
+ expect(ink).toMatch(/^#|^rgb|^hsl/);
92
+ expect(paintedColor(label)?.toLowerCase()).toBe(ink);
93
+ expect(paintedColor(frame)?.toLowerCase()).toBe(ink);
94
+ });
95
+
96
+ it(`${scheme}: the hueless house theme tints with the primary ink, not the grey accent step`, () => {
97
+ houseRender(
98
+ <Button chromeless accentInk>
99
+ Components
100
+ </Button>,
101
+ scheme,
102
+ );
103
+ expect(greyAccent()).not.toBe(accentInk());
104
+ expect(paintedColor(screen.getByText("Components"))?.toLowerCase()).not.toBe(greyAccent());
105
+ });
106
+
107
+ it(`${scheme}: under a hue the tint is the hue`, () => {
108
+ houseRender(
109
+ <Button chromeless accentInk>
110
+ Components
111
+ </Button>,
112
+ `${scheme}_blue`,
113
+ );
114
+ expect(accentInk()).toBe(greyAccent());
115
+ expect(paintedColor(screen.getByText("Components"))?.toLowerCase()).toBe(accentInk());
116
+ });
117
+
118
+ it(`${scheme}: without accentInk a chromeless button keeps its neutral ink`, () => {
119
+ houseRender(<Button chromeless>Components</Button>, `${scheme}_blue`);
120
+ const label = screen.getByText("Components");
121
+ expect(paintedColor(label)?.toLowerCase()).not.toBe(accentInk());
122
+ });
123
+
124
+ it(`${scheme}: a disabled accentInk button leaves the tint`, () => {
125
+ houseRender(
126
+ <Button chromeless accentInk disabled disabledReason="Nothing to go back to">
127
+ Components
128
+ </Button>,
129
+ scheme,
130
+ );
131
+ const label = screen.getByText("Components");
132
+ expect(paintedColor(label)?.toLowerCase()).not.toBe(accentInk());
133
+ });
134
+ }
135
+ });
@@ -31,6 +31,7 @@ import {
31
31
  solidPaletteThemes,
32
32
  useAccentOnSurface,
33
33
  useBarSurface,
34
+ useIdentityInkOnSurface,
34
35
  useResolvedKnobs,
35
36
  useTouchSurface,
36
37
  type ControlTier,
@@ -372,6 +373,13 @@ export interface ButtonProps extends Omit<
372
373
  * that paints a frame: the frame is what aligns there.
373
374
  */
374
375
  edge?: "start" | "end";
376
+ /**
377
+ * LC-145: a chromeless bar button's label and glyphs take the host's
378
+ * identity ink (`useIdentityInkOnSurface`), the iOS bar-button tint: the
379
+ * theme's hue, or the primary ink when the theme has none. No effect on a
380
+ * button that paints a frame, while selected, or disabled.
381
+ */
382
+ accentInk?: boolean;
375
383
  }
376
384
 
377
385
  export function Button({
@@ -397,6 +405,7 @@ export function Button({
397
405
  circular,
398
406
  chromeless,
399
407
  edge,
408
+ accentInk,
400
409
  verb,
401
410
  noun,
402
411
  actionRole,
@@ -473,6 +482,7 @@ export function Button({
473
482
  const parentScopeTheme = useTheme();
474
483
  const pageOutlineColor = parentScopeTheme.outlineColor?.val as string | undefined;
475
484
  const hostAccentInk = useAccentOnSurface();
485
+ const hostIdentityInk = useIdentityInkOnSurface();
476
486
  const grouped = useControlGrouped();
477
487
  // LC-153 CONTROL-TIERS: a bar paints its neutral buttons plain; content
478
488
  // paints them tinted. The scheme picks the wash step.
@@ -646,6 +656,7 @@ export function Button({
646
656
  const isSelected =
647
657
  selected ?? (ariaPressedProp === true || ariaPressedProp === "true" ? true : undefined);
648
658
  const selectedFill = Boolean(isSelected) && !unavailable;
659
+ const barInk = accentInk && chromeless && !selectedFill && !unavailable ? hostIdentityInk : null;
649
660
  const iconSize = nested
650
661
  ? Math.min(recipe.iconSize, Math.max(12, Math.round(knobProps.nestedControl.px * 0.45)))
651
662
  : recipe.iconSize;
@@ -683,6 +694,7 @@ export function Button({
683
694
  : outlinedInk
684
695
  ? { color: outlinedInk }
685
696
  : undefined)}
697
+ {...(barInk ? { color: barInk } : undefined)}
686
698
  // LC-40: on the washed disabled chrome the label leaves the on-fill
687
699
  // foreground for the wash's AA ink (keepLabel keeps >=4.5:1).
688
700
  {...(unavailable ? disabledText : undefined)}
@@ -711,6 +723,7 @@ export function Button({
711
723
  const nativeIconColor =
712
724
  props.color ??
713
725
  (unavailable ? disabledText.color : undefined) ??
726
+ barInk ??
714
727
  (textColor
715
728
  ? outlinedOnHost
716
729
  ? hostScoped({ color: textColor }).color
@@ -868,6 +881,7 @@ export function Button({
868
881
  // (LC-05); outlined frames sit on the page, so re-anchor inherited
869
882
  // text/icon color (currentColor) to the hue's readable text tier.
870
883
  {...(outlinedInk ? { color: outlinedInk } : undefined)}
884
+ {...(barInk ? { color: barInk } : undefined)}
871
885
  // LC-40 DISABLED-VISIBLE: keepLabel washes the chrome (muted fill/
872
886
  // border) while label/glyphs re-anchor to the lowest AA text tier
873
887
  // (`color` inherits into currentColor icons); dimWhole dims the
@@ -36,7 +36,7 @@ describe("FieldGroup design-law (MPO-60)", () => {
36
36
  expect(["400", "normal"]).toContain(String(weight));
37
37
  });
38
38
 
39
- it("field gap is the within-group role at any size token, never sizeToken", () => {
39
+ it("field gap is the field rhythm role (fieldGap $4, LC-154) at any size token, never sizeToken", () => {
40
40
  const result = renderWithProviders(
41
41
  <Surface size="$5">
42
42
  <FieldGroup legend="Billing">
@@ -45,11 +45,11 @@ describe("FieldGroup design-law (MPO-60)", () => {
45
45
  </Surface>,
46
46
  );
47
47
  const group = result.container.querySelector("[data-mpo-field-group]");
48
- expect(group?.getAttribute("data-field-gap")).toBe("$3");
48
+ expect(group?.getAttribute("data-field-gap")).toBe("$4");
49
49
  expect(group?.getAttribute("data-size-token")).toBe("$5");
50
50
  });
51
51
 
52
- it("has no compact switch: a smaller Surface changes the token, not the gap", () => {
52
+ it("has no compact switch: a smaller Surface changes the token, not the gap (LC-154)", () => {
53
53
  const result = renderWithProviders(
54
54
  <Surface size="$3">
55
55
  <FieldGroup legend="Small">
@@ -59,7 +59,7 @@ describe("FieldGroup design-law (MPO-60)", () => {
59
59
  );
60
60
  const group = result.container.querySelector("[data-mpo-field-group]");
61
61
  expect(group?.getAttribute("data-size-token")).toBe("$3");
62
- expect(group?.getAttribute("data-field-gap")).toBe("$3");
62
+ expect(group?.getAttribute("data-field-gap")).toBe("$4");
63
63
  expect(group?.hasAttribute("data-density")).toBe(false);
64
64
  });
65
65
  });
@@ -374,7 +374,7 @@ describe("Form", () => {
374
374
  });
375
375
 
376
376
  describe("layout recipes (DG-FORM-01/06, LC-73)", () => {
377
- it("owns vertical rhythm from the within-group role, not sizeToken", () => {
377
+ it("owns vertical rhythm from the field rhythm role (fieldGap $4, LC-154), not sizeToken", () => {
378
378
  const result = renderWithProviders(
379
379
  <Surface size="$5">
380
380
  <Form onSubmit={vi.fn()} aria-label="Layout Form">
@@ -383,12 +383,12 @@ describe("Form", () => {
383
383
  </Surface>,
384
384
  );
385
385
  const layout = result.container.querySelector("[data-mpo-form-layout]");
386
- expect(layout?.getAttribute("data-gap")).toBe("$3");
386
+ expect(layout?.getAttribute("data-gap")).toBe("$4");
387
387
  expect(layout?.getAttribute("data-size-token")).toBe("$5");
388
388
  expect(layout?.hasAttribute("data-density")).toBe(false);
389
389
  });
390
390
 
391
- it("size forwards a token to every field and keeps the rhythm on the role", () => {
391
+ it("size forwards a token to every field and keeps the rhythm on the fieldGap role (LC-154)", () => {
392
392
  const result = renderWithProviders(
393
393
  <Form size="$3" onSubmit={vi.fn()}>
394
394
  <Button action="submit" size="$5">
@@ -398,7 +398,7 @@ describe("Form", () => {
398
398
  );
399
399
  const layout = result.container.querySelector("[data-mpo-form-layout]");
400
400
  expect(layout?.getAttribute("data-size-token")).toBe("$3");
401
- expect(layout?.getAttribute("data-gap")).toBe("$3");
401
+ expect(layout?.getAttribute("data-gap")).toBe("$4");
402
402
  expect(result.getByRole("button").getAttribute("data-size-token")).toBe("$3");
403
403
  });
404
404
 
@@ -53,8 +53,8 @@ function stubContainerWidth(width: number) {
53
53
  }
54
54
 
55
55
  describe("formSpacing tokens", () => {
56
- it("encodes section gap larger than field gap (DG-FORM-06)", () => {
57
- expect(formFieldGap).toBe("$3");
56
+ it("encodes section gap larger than field gap (DG-FORM-06; field rhythm $4, LC-154)", () => {
57
+ expect(formFieldGap).toBe("$4");
58
58
  expect(formSectionGap).toBe("$8");
59
59
  expect(Number(formSectionGap.slice(1))).toBeGreaterThan(Number(formFieldGap.slice(1)));
60
60
  });
@@ -78,7 +78,8 @@ describe("FormGrid", () => {
78
78
  expect(grid?.getAttribute("data-columns")).toBe("1");
79
79
  expect(grid?.getAttribute("data-requested-columns")).toBe("1");
80
80
  expect(grid?.getAttribute("data-max-columns")).toBe("2");
81
- expect(grid?.getAttribute("data-gap")).toBe("$3");
81
+ // LC-154: fields stack at the fieldGap role.
82
+ expect(grid?.getAttribute("data-gap")).toBe("$4");
82
83
  expect(grid?.getAttribute("data-size-token")).toBe("$true");
83
84
  });
84
85
 
@@ -129,7 +130,7 @@ describe("FormGrid", () => {
129
130
  expect(grid.style.gridTemplateColumns).toBe("repeat(1, minmax(0, 1fr))");
130
131
  });
131
132
 
132
- it("gap is the within-group role at any size token, never sizeToken", () => {
133
+ it("gap is the field rhythm role (fieldGap $4, LC-154) at any size token, never sizeToken", () => {
133
134
  const result = renderWithProviders(
134
135
  <Surface size="$5">
135
136
  <FormGrid>
@@ -138,7 +139,7 @@ describe("FormGrid", () => {
138
139
  </Surface>,
139
140
  );
140
141
  const grid = result.container.querySelector("[data-mpo-form-grid]");
141
- expect(grid?.getAttribute("data-gap")).toBe("$3");
142
+ expect(grid?.getAttribute("data-gap")).toBe("$4");
142
143
  expect(grid?.getAttribute("data-size-token")).toBe("$5");
143
144
  });
144
145
 
@@ -153,10 +154,10 @@ describe("FormGrid", () => {
153
154
  const grid = result.container.querySelector("[data-mpo-form-grid]");
154
155
  expect(grid?.hasAttribute("data-density")).toBe(false);
155
156
  expect(grid?.getAttribute("data-size-token")).toBe("$3");
156
- expect(grid?.getAttribute("data-gap")).toBe("$3");
157
+ expect(grid?.getAttribute("data-gap")).toBe("$4");
157
158
  });
158
159
 
159
- it("spreads the role gap fragment (not a raw 16)", () => {
160
+ it("spreads the fieldGap role fragment (not a raw 16, LC-154)", () => {
160
161
  const result = renderWithProviders(
161
162
  <Preset>
162
163
  <FormGrid>
@@ -165,7 +166,7 @@ describe("FormGrid", () => {
165
166
  </Preset>,
166
167
  );
167
168
  const grid = result.container.querySelector("[data-mpo-form-grid]");
168
- expect(grid?.getAttribute("data-gap")).toBe("$3");
169
+ expect(grid?.getAttribute("data-gap")).toBe("$4");
169
170
  });
170
171
  });
171
172
 
@@ -195,7 +196,7 @@ describe("FieldGroup", () => {
195
196
  ).toBe("2");
196
197
  });
197
198
 
198
- it("field gap is the within-group role, not the size token", () => {
199
+ it("field gap is the field rhythm role (fieldGap $4, LC-154), not the size token", () => {
199
200
  const result = renderWithProviders(
200
201
  <Surface size="$5">
201
202
  <FieldGroup legend="Billing">
@@ -204,7 +205,7 @@ describe("FieldGroup", () => {
204
205
  </Surface>,
205
206
  );
206
207
  const group = result.container.querySelector("[data-mpo-field-group]");
207
- expect(group?.getAttribute("data-field-gap")).toBe("$3");
208
+ expect(group?.getAttribute("data-field-gap")).toBe("$4");
208
209
  expect(group?.getAttribute("data-size-token")).toBe("$5");
209
210
  });
210
211
  });
@@ -257,7 +258,7 @@ describe("FormSection", () => {
257
258
  });
258
259
 
259
260
  describe("FormSection semantic gaps (DG-FORM-06)", () => {
260
- it("defaults field gap to knobProps.gap and section gap to knobProps.gapLg", () => {
261
+ it("defaults field gap to knobProps.fieldGap (LC-154) and section gap to knobProps.gapLg", () => {
261
262
  const result = renderWithProviders(
262
263
  <FormSectionStack>
263
264
  <FormSection label="A">
@@ -267,8 +268,8 @@ describe("FormSection semantic gaps (DG-FORM-06)", () => {
267
268
  );
268
269
  const section = result.container.querySelector("[data-mpo-form-section]");
269
270
  const stack = result.container.querySelector("[data-mpo-form-section-stack]");
270
- // knob defaults: gap $4 (within-group), gapLg $5 (between-groups)
271
- expect(section?.getAttribute("data-field-gap")).toBe("$3");
271
+ // knob defaults: fieldGap $4 (field to field, LC-154), gapLg $5 (between groups)
272
+ expect(section?.getAttribute("data-field-gap")).toBe("$4");
272
273
  expect(stack?.getAttribute("data-section-gap")).toBe("$5");
273
274
  const fieldStep = Number(section?.getAttribute("data-field-gap")?.slice(1));
274
275
  const sectionStep = Number(stack?.getAttribute("data-section-gap")?.slice(1));
@@ -287,7 +288,7 @@ describe("FormSection semantic gaps (DG-FORM-06)", () => {
287
288
  );
288
289
  const section = result.container.querySelector("[data-mpo-form-section]");
289
290
  const stack = result.container.querySelector("[data-mpo-form-section-stack]");
290
- expect(section?.getAttribute("data-field-gap")).toBe("$3");
291
+ expect(section?.getAttribute("data-field-gap")).toBe("$4");
291
292
  expect(stack?.getAttribute("data-section-gap")).toBe("$5");
292
293
  });
293
294
 
@@ -266,7 +266,7 @@ describe("FieldLayout banned-error-word DEV warn", () => {
266
266
  });
267
267
 
268
268
  describe("FieldLayout design-law (MPO-60)", () => {
269
- it("paints the label at weight 400 (R7 / §6.1)", () => {
269
+ it("paints the label role at weight 500 (LC-152)", () => {
270
270
  const { container } = renderWithProviders(
271
271
  <FieldLayout id="w" label="Name">
272
272
  <TamaguiInput id="w" />
@@ -275,7 +275,7 @@ describe("FieldLayout design-law (MPO-60)", () => {
275
275
  const label = container.querySelector("label") as HTMLElement;
276
276
  expect(label).toBeTruthy();
277
277
  const weight = label.style.fontWeight || getComputedStyle(label).fontWeight;
278
- expect(["400", "normal"]).toContain(String(weight));
278
+ expect(String(weight)).toBe("500");
279
279
  });
280
280
 
281
281
  it("does not pass a control-height sizeToken to the label (LC-73)", () => {
@@ -323,12 +323,12 @@ describe("FieldLayout design-law (MPO-60)", () => {
323
323
  });
324
324
  });
325
325
 
326
- describe("label body stop and weight knob", () => {
326
+ describe("label role stop and weight knob (LC-152)", () => {
327
327
  for (const [fontWeight, expected] of [
328
- ["regular", "400"],
328
+ ["regular", "500"],
329
329
  ["bold", "700"],
330
330
  ] as const) {
331
- it(`uses body $true and ${fontWeight} weight`, () => {
331
+ it(`uses the label role $2 (13px) and ${fontWeight} weight`, () => {
332
332
  const { container } = renderWithProviders(
333
333
  <TamaguiProvider config={bodyConfig} defaultTheme="light">
334
334
  <Preset overrides={{ fontWeight }}>
@@ -340,8 +340,8 @@ describe("label body stop and weight knob", () => {
340
340
  { tamagui: false },
341
341
  );
342
342
  const label = container.querySelector("label")!;
343
- expect(label.className).toContain("_fos-f-size-true");
344
- expect(getComputedStyle(label).fontSize).toBe("15px");
343
+ expect(label.className).toContain("_fos-f-size-2");
344
+ expect(getComputedStyle(label).fontSize).toBe("13px");
345
345
  expect(getComputedStyle(label).fontWeight).toBe(expected);
346
346
  });
347
347
  }
@@ -320,6 +320,27 @@ describe("Progress", () => {
320
320
  expect(radiusAtoms).not.toContain("_btlr-100000px");
321
321
  });
322
322
 
323
+ it("rounds the fill's leading end at large and full, where the rail is already a capsule (LC-132)", () => {
324
+ const fillRadiusAtoms = (borderRadius: "medium" | "large" | "full") => {
325
+ const { container, unmount } = renderWithProviders(
326
+ <Preset overrides={{ borderRadius }}>
327
+ <Progress label="Fill end" value={50} />
328
+ </Preset>,
329
+ );
330
+ const atoms = String(getIndicator(container)?.className || "")
331
+ .split(" ")
332
+ .filter((c) => c.startsWith("_bbrr-"));
333
+ unmount();
334
+ return atoms;
335
+ };
336
+ const medium = fillRadiusAtoms("medium");
337
+ const large = fillRadiusAtoms("large");
338
+ const full = fillRadiusAtoms("full");
339
+ expect(medium).toContain("_bbrr-t-radius-0");
340
+ expect(large).toContain("_bbrr-t-radius-2");
341
+ expect(full).toContain("_bbrr-t-radius-12");
342
+ });
343
+
323
344
  it("skeleton track height matches the live 0.25× size formula, not a 6px leftover", () => {
324
345
  const live = renderWithProviders(<Progress label="Upload" value={50} />);
325
346
  const bar = live.container.querySelector("[role='progressbar']") as HTMLElement;
@@ -1,7 +1,13 @@
1
1
  import { transitionOrInstant } from "../../shared/instantMotion";
2
2
  import type { ReactNode } from "react";
3
3
  import type { AnyFormApi } from "../../types";
4
- import { Intent, transitionProps, type MotionRole } from "@multiplatform.one/theme";
4
+ import {
5
+ Intent,
6
+ radiusStopFromToken,
7
+ railFillRadius,
8
+ transitionProps,
9
+ type MotionRole,
10
+ } from "@multiplatform.one/theme";
5
11
  import type { KnobProps } from "@multiplatform.one/theme";
6
12
  import { getSize } from "@tamagui/get-token";
7
13
  import type { LabelProps, SizeTokens } from "tamagui";
@@ -86,11 +92,18 @@ function indicatorMotion(role: MotionRole) {
86
92
  * `ProgressIndicator` component sub-theme. Capturing here keeps the hue
87
93
  * scope explicit. useTheme() subscribes, so scheme changes stay live.
88
94
  */
89
- function HueFillIndicator({ motion }: { motion: MotionRole }) {
95
+ function HueFillIndicator({
96
+ motion,
97
+ radius,
98
+ }: {
99
+ motion: MotionRole;
100
+ radius: ReturnType<typeof railFillRadius>;
101
+ }) {
90
102
  const theme = useTheme();
91
103
  const fill = theme.color9?.val;
92
104
  return (
93
105
  <ProgressIndicator
106
+ borderRadius={radius}
94
107
  {...(fill ? { backgroundColor: fill as any } : undefined)}
95
108
  {...indicatorMotion(motion)}
96
109
  />
@@ -176,6 +189,7 @@ function ProgressTrack({
176
189
  const trackSize = size ?? sizeToken;
177
190
  const percent = Math.round((progressValue / max) * 100);
178
191
  const motion = indicatorMotion(knobProps.motion.slide);
192
+ const fillRadius = railFillRadius(radiusStopFromToken(knobProps.borderRadius.borderRadius));
179
193
  const name = typeof label === "string" && label ? label : t("Progress");
180
194
 
181
195
  const progressElement = (
@@ -203,12 +217,13 @@ function ProgressTrack({
203
217
  >
204
218
  {intent && intent !== "accent" ? (
205
219
  <Intent name={intent}>
206
- <HueFillIndicator motion={knobProps.motion.slide} />
220
+ <HueFillIndicator motion={knobProps.motion.slide} radius={fillRadius} />
207
221
  </Intent>
208
222
  ) : (
209
223
  // Accent fills use the mark channel without a solid sub-theme scope.
210
224
  // The default fill must hold 3:1 against both the track and page (DF-06).
211
225
  <ProgressIndicator
226
+ borderRadius={fillRadius}
212
227
  backgroundColor={
213
228
  intent === "accent" ? formSelectedColors.mark : formControlColors.boundary
214
229
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Fallback form rhythm tokens (DG-FORM-06). Prefer `knobProps.gap` /
2
+ * Fallback form rhythm tokens (DG-FORM-06). Prefer `knobProps.fieldGap` /
3
3
  * `knobProps.gapLg` (the spacing roles) at the layout primitives. These are
4
- * the roles' `$space` keys, not a size mapping.
4
+ * the roles' `$space` keys, not a size mapping; the field step is LC-154's.
5
5
  */
6
- export const formFieldGap = "$3" as const;
6
+ export const formFieldGap = "$4" as const;
7
7
  export const formSectionGap = "$8" as const;
8
8
 
9
9
  /** Default readable form width (DG-FORM-08 ~480–600px). */
@@ -128,8 +128,15 @@ export interface ButtonProps extends Omit<TamaguiButtonProps, "form" | "icon" |
128
128
  * that paints a frame: the frame is what aligns there.
129
129
  */
130
130
  edge?: "start" | "end";
131
+ /**
132
+ * LC-145: a chromeless bar button's label and glyphs take the host's
133
+ * identity ink (`useIdentityInkOnSurface`), the iOS bar-button tint: the
134
+ * theme's hue, or the primary ink when the theme has none. No effect on a
135
+ * button that paints a frame, while selected, or disabled.
136
+ */
137
+ accentInk?: boolean;
131
138
  }
132
- export declare function Button({ children, action, form: formProp, loading, accent, error, warning, success, skeleton, outlined, disabled, theme, onPress, onKeyDown, tabIndex, icon, iconAfter, selected, nested, circular, chromeless, edge, verb, noun, actionRole, disabledReason, disabledReasonPlacement, sizeRecipeEscape, recipeFamily, height: heightProp, paddingHorizontal: paddingHorizontalProp, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
139
+ export declare function Button({ children, action, form: formProp, loading, accent, error, warning, success, skeleton, outlined, disabled, theme, onPress, onKeyDown, tabIndex, icon, iconAfter, selected, nested, circular, chromeless, edge, accentInk, verb, noun, actionRole, disabledReason, disabledReasonPlacement, sizeRecipeEscape, recipeFamily, height: heightProp, paddingHorizontal: paddingHorizontalProp, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
133
140
  export declare namespace Button {
134
141
  var Icon: (props: {
135
142
  children: React.ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Button/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Button/index.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/Progress/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9C,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAEL,KAAK,aAAa,IAAI,oBAAoB,EAQ3C,MAAM,SAAS,CAAC;AA8BjB,oEAAoE;AACpE,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAmDxE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAClC,aAAa,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,KAAK,GAAG,IAAI,GAAG,UAAU,CAAC,CAAC;IAChF;;;;;OAKG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAwHD,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EACJ,IAAI,EAAE,QAAQ,EACd,UAAU,EACV,KAAK,EACL,UAAU,EACV,UAAU,EACV,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,EAAE,EAAE,MAAM,EACV,KAAK,EACL,YAAY,EACZ,GAAG,EACH,QAAQ,EAAE,SAAS,EACnB,MAAM,EACN,aAAa,EACb,MAAM,EACN,QAAQ,EACR,SAAS,EAAE,aAAa,GACzB,EAAE,aAAa,2CAuGf"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/Progress/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAU9C,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAEL,KAAK,aAAa,IAAI,oBAAoB,EAQ3C,MAAM,SAAS,CAAC;AA8BjB,oEAAoE;AACpE,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AA0DxE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAClC,aAAa,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,KAAK,GAAG,IAAI,GAAG,UAAU,CAAC,CAAC;IAChF;;;;;OAKG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA0HD,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EACJ,IAAI,EAAE,QAAQ,EACd,UAAU,EACV,KAAK,EACL,UAAU,EACV,UAAU,EACV,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,EAAE,EAAE,MAAM,EACV,KAAK,EACL,YAAY,EACZ,GAAG,EACH,QAAQ,EAAE,SAAS,EACnB,MAAM,EACN,aAAa,EACb,MAAM,EACN,QAAQ,EACR,SAAS,EAAE,aAAa,GACzB,EAAE,aAAa,2CAuGf"}
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Fallback form rhythm tokens (DG-FORM-06). Prefer `knobProps.gap` /
2
+ * Fallback form rhythm tokens (DG-FORM-06). Prefer `knobProps.fieldGap` /
3
3
  * `knobProps.gapLg` (the spacing roles) at the layout primitives. These are
4
- * the roles' `$space` keys, not a size mapping.
4
+ * the roles' `$space` keys, not a size mapping; the field step is LC-154's.
5
5
  */
6
- export declare const formFieldGap: "$3";
6
+ export declare const formFieldGap: "$4";
7
7
  export declare const formSectionGap: "$8";
8
8
  /** Default readable form width (DG-FORM-08 ~480–600px). */
9
9
  export declare const formReadableMaxWidth = 560;