@multiplatform.one/theme 7.43.0 → 7.44.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/theme",
3
- "version": "7.43.0",
3
+ "version": "7.44.0",
4
4
  "description": "Tamagui theme system for multiplatform.one",
5
5
  "keywords": [
6
6
  "multiplatform",
@@ -59,8 +59,8 @@
59
59
  "access": "public"
60
60
  },
61
61
  "dependencies": {
62
- "@multiplatform.one/platform": "^7.43.0",
63
- "@multiplatform.one/store": "^7.43.0",
62
+ "@multiplatform.one/platform": "^7.44.0",
63
+ "@multiplatform.one/store": "^7.44.0",
64
64
  "@tamagui/animations-react-native": "2.7.6",
65
65
  "@tamagui/colors": "2.7.6",
66
66
  "@tamagui/config": "2.7.6",
@@ -73,8 +73,8 @@
73
73
  "react-cookie": "^8.1.2"
74
74
  },
75
75
  "devDependencies": {
76
- "@multiplatform.one/config": "7.43.0",
77
- "@multiplatform.one/test-utils": "7.43.0",
76
+ "@multiplatform.one/config": "7.44.0",
77
+ "@multiplatform.one/test-utils": "7.44.0",
78
78
  "@tamagui/animations-css": "2.7.6",
79
79
  "@tamagui/animations-reanimated": "2.7.6",
80
80
  "@tamagui/font-inter": "2.7.6",
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createThemesBuilder } from "./createThemes";
3
+ import { defaultAccentTheme } from "./defaults/accent";
4
+ import { defaultBaseTheme } from "./defaults/base";
5
+ import { defaultBuilderOptions } from "./defaults/builderOptions";
6
+ import {
7
+ FIELD_FOCUS_COLOR,
8
+ FIELD_FOCUS_REST,
9
+ FIELD_FOCUS_STYLE,
10
+ fieldFocusCss,
11
+ fieldFocusHoldsContrast,
12
+ } from "./fieldFocus";
13
+
14
+ const themes = createThemesBuilder(
15
+ defaultBaseTheme,
16
+ defaultAccentTheme,
17
+ defaultBuilderOptions,
18
+ ).themes() as Record<string, Record<string, string>>;
19
+
20
+ describe("FZ-7717: one field focus look", () => {
21
+ it("is the accent border at the same width, with no ring, gap or shadow", () => {
22
+ expect(FIELD_FOCUS_STYLE.borderColor).toBe("$accentColor");
23
+ expect(FIELD_FOCUS_COLOR).toBe("$accentColor");
24
+ expect(FIELD_FOCUS_STYLE.outlineWidth).toBe(0);
25
+ expect(FIELD_FOCUS_REST.outlineWidth).toBe(0);
26
+ for (const key of Object.keys(FIELD_FOCUS_STYLE)) {
27
+ expect(key).not.toMatch(/^(borderWidth|outlineOffset|shadow|elevation|boxShadow)/);
28
+ }
29
+ });
30
+
31
+ it("web css paints the accent border and drops the ring on pointer and keyboard focus", () => {
32
+ const css = fieldFocusCss("[data-mp-input-box]");
33
+ expect(css).toContain("[data-mp-input-box]:has(input:focus)");
34
+ expect(css).toContain("[data-mp-input-box]:focus-visible");
35
+ expect(css).toContain("border-color: var(--accentColor");
36
+ expect(css).toContain("outline: none");
37
+ expect(css).not.toMatch(/outline-offset|box-shadow|border-width/);
38
+ });
39
+
40
+ // WCAG 2.4.7 / 1.4.11: the focused border must hold 3:1 against the field
41
+ // fill ($background) and the grounds a field sits on, in every colour theme.
42
+ for (const [name, theme] of Object.entries(themes)) {
43
+ if (!/^(light|dark)(_(blue|gray|green|orange|pink|purple|red|yellow|neutral))?$/.test(name))
44
+ continue;
45
+ it(`${name}: $accentColor holds 3:1 on the field fill and page grounds`, () => {
46
+ expect(theme.accentColor, name).toBeTruthy();
47
+ expect(
48
+ fieldFocusHoldsContrast(theme.accentColor, [
49
+ theme.background,
50
+ theme.color1,
51
+ theme.color2,
52
+ theme.color3,
53
+ ]),
54
+ `${name}: ${theme.accentColor} on ${theme.background}`,
55
+ ).toBe(true);
56
+ });
57
+ }
58
+ });
@@ -0,0 +1,54 @@
1
+ import { focusRingColorMeetsContrast } from "./focusState";
2
+
3
+ /**
4
+ * FZ-7717: the ONE focus look for every focusable MPO field (Input, TextArea,
5
+ * Select, Duration, Date and Time, Number, OTP and every other Input.Box).
6
+ * Clay's pick (rv_a60d41af5f9beea6): the field's own border turns the accent
7
+ * colour at the width it already has. No ring, no gap, no shadow.
8
+ *
9
+ * `$accentColor` is the accent ink, held at 4.5:1 on color1..color3 by
10
+ * holdAccentFloor, so the border clears the 3:1 focus floor on every field
11
+ * ground. Fields spread these fragments; they never define their own.
12
+ */
13
+ export const FIELD_FOCUS_COLOR = "$accentColor";
14
+
15
+ export const FIELD_FOCUS_STYLE = {
16
+ borderColor: FIELD_FOCUS_COLOR,
17
+ outlineWidth: 0,
18
+ outlineStyle: "solid" as const,
19
+ outlineColor: "transparent",
20
+ };
21
+
22
+ /** The same field at rest: no outline left over from a ring recipe. */
23
+ export const FIELD_FOCUS_REST = {
24
+ outlineWidth: 0,
25
+ outlineStyle: "solid" as const,
26
+ outlineColor: "transparent",
27
+ };
28
+
29
+ /**
30
+ * Web: the focused element is the inner <input>, not the painted Box, so the
31
+ * Box takes the look through `:has()`. Covers pointer and keyboard focus;
32
+ * `:focus-visible` on the Box covers button-like Boxes (Select, Date).
33
+ * `deep` also lights the Box when any inner control has keyboard focus, for
34
+ * Boxes whose adornment is the field itself (PhoneInput's country picker).
35
+ * A Box with a separate inner action (SearchInput's clear) stays shallow.
36
+ */
37
+ export function fieldFocusCss(box: string, { deep = false }: { deep?: boolean } = {}): string {
38
+ const selectors = [
39
+ `${box}:has(input:focus)`,
40
+ `${box}:has(textarea:focus)`,
41
+ `${box}:has([data-mp-input-area]:focus)`,
42
+ `${box}:focus-visible`,
43
+ ...(deep ? [`${box}:has(:focus-visible)`] : []),
44
+ ];
45
+ return `${selectors.join(",\n")} {
46
+ border-color: var(--accentColor, CanvasText) !important;
47
+ outline: none !important;
48
+ }`;
49
+ }
50
+
51
+ /** True when the accent border clears 3:1 against every given field ground. */
52
+ export function fieldFocusHoldsContrast(accent: string, grounds: string[]): boolean {
53
+ return grounds.every((ground) => focusRingColorMeetsContrast(accent, ground));
54
+ }
@@ -8,6 +8,7 @@ export * from "./createDefaultThemeConfig";
8
8
  export * from "./createThemes";
9
9
  export * from "./defaults/index";
10
10
  export * from "./devtools/ThemeDevtoolsPanel";
11
+ export * from "./fieldFocus";
11
12
  export * from "./focusState";
12
13
  export * from "./glyphPaint";
13
14
  export * from "./fontCategoryMetrics";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * FZ-7717: the ONE focus look for every focusable MPO field (Input, TextArea,
3
+ * Select, Duration, Date and Time, Number, OTP and every other Input.Box).
4
+ * Clay's pick (rv_a60d41af5f9beea6): the field's own border turns the accent
5
+ * colour at the width it already has. No ring, no gap, no shadow.
6
+ *
7
+ * `$accentColor` is the accent ink, held at 4.5:1 on color1..color3 by
8
+ * holdAccentFloor, so the border clears the 3:1 focus floor on every field
9
+ * ground. Fields spread these fragments; they never define their own.
10
+ */
11
+ export declare const FIELD_FOCUS_COLOR = "$accentColor";
12
+ export declare const FIELD_FOCUS_STYLE: {
13
+ borderColor: string;
14
+ outlineWidth: number;
15
+ outlineStyle: "solid";
16
+ outlineColor: string;
17
+ };
18
+ /** The same field at rest: no outline left over from a ring recipe. */
19
+ export declare const FIELD_FOCUS_REST: {
20
+ outlineWidth: number;
21
+ outlineStyle: "solid";
22
+ outlineColor: string;
23
+ };
24
+ /**
25
+ * Web: the focused element is the inner <input>, not the painted Box, so the
26
+ * Box takes the look through `:has()`. Covers pointer and keyboard focus;
27
+ * `:focus-visible` on the Box covers button-like Boxes (Select, Date).
28
+ * `deep` also lights the Box when any inner control has keyboard focus, for
29
+ * Boxes whose adornment is the field itself (PhoneInput's country picker).
30
+ * A Box with a separate inner action (SearchInput's clear) stays shallow.
31
+ */
32
+ export declare function fieldFocusCss(box: string, { deep }?: {
33
+ deep?: boolean;
34
+ }): string;
35
+ /** True when the accent border clears 3:1 against every given field ground. */
36
+ export declare function fieldFocusHoldsContrast(accent: string, grounds: string[]): boolean;
37
+ //# sourceMappingURL=fieldFocus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldFocus.d.ts","sourceRoot":"","sources":["../../src/theme/fieldFocus.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,eAAO,MAAM,iBAAiB;;;;;CAK7B,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,gBAAgB;;;;CAI5B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,IAAY,EAAE,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAY5F;AAED,+EAA+E;AAC/E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAElF"}
@@ -8,6 +8,7 @@ export * from "./createDefaultThemeConfig";
8
8
  export * from "./createThemes";
9
9
  export * from "./defaults/index";
10
10
  export * from "./devtools/ThemeDevtoolsPanel";
11
+ export * from "./fieldFocus";
11
12
  export * from "./focusState";
12
13
  export * from "./glyphPaint";
13
14
  export * from "./fontCategoryMetrics";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,MAAM,IAAI,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACjF,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAElC,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAElC,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,eAAe,IAAI,kBAAkB,EACrC,kBAAkB,EAClB,WAAW,EACX,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,IAAI,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEpF,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,MAAM,IAAI,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACjF,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAElC,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAElC,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,eAAe,IAAI,kBAAkB,EACrC,kBAAkB,EAClB,WAAW,EACX,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,IAAI,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEpF,cAAc,YAAY,CAAC"}