@jobber/components-native 0.113.0 → 0.113.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.113.0",
3
+ "version": "0.113.2",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -53,8 +53,7 @@
53
53
  "compile": "tsc -p tsconfig.build.json",
54
54
  "build:clean": "rm -rf ./dist",
55
55
  "storybook": "storybook dev -p 6008 --disable-telemetry",
56
- "storybook:build": "storybook build --disable-telemetry",
57
- "lint:locales": "node scripts/check-locale-parity.mjs"
56
+ "storybook:build": "storybook build --disable-telemetry"
58
57
  },
59
58
  "dependencies": {
60
59
  "@react-native-clipboard/clipboard": "^1.11.2",
@@ -125,5 +124,5 @@
125
124
  "react-native-screens": ">=4.18.0",
126
125
  "react-native-svg": ">=12.0.0"
127
126
  },
128
- "gitHead": "25cd8815738e86b9f49311096e7d4ffd02d843b5"
127
+ "gitHead": "106e281adc9a3b0e87e125e1a9f1c945ce2de3cd"
129
128
  }
@@ -19,6 +19,26 @@ const RADIUS = 20;
19
19
  // "200" used as the gap value exceeds the circumference, ensuring only
20
20
  // one stroke segment is visible at a time.
21
21
  const GAP = 200;
22
+ /**
23
+ * Per-size motion profile. Adding a size is an additive map entry — not a
24
+ * new boolean branch. `layers: 1` is Layer 1 only (fixed arc); `layers: 3`
25
+ * runs the full Material 3 indeterminate ring (Layers 1–3).
26
+ *
27
+ * Small matches web's ActivityIndicator.module.css (`.small .svg` /
28
+ * `.small .arc`): Layer 1 only, spun ~1.8× faster. Web hardcodes 1000ms
29
+ * (≈1803ms ÷ 1.8); we match that absolute duration.
30
+ */
31
+ const MOTION_BY_SIZE = {
32
+ small: {
33
+ layers: 1,
34
+ linearRotateMs: 1000,
35
+ dasharray: `40, ${GAP}`,
36
+ },
37
+ base: {
38
+ layers: 3,
39
+ linearRotateMs: tokens["timing-indicator--linear-rotate"],
40
+ },
41
+ };
22
42
  function resolveLegacySize(size) {
23
43
  if (size === "large") {
24
44
  if (__DEV__) {
@@ -61,23 +81,28 @@ export function ActivityIndicator({ size = "base", accessibilityLabel, style, te
61
81
  if (reducedMotion) {
62
82
  return (React.createElement(ReducedMotionRing, { pixelSize: pixelSize, strokeWidth: strokeWidth, ringColor: themeTokens["color-icon"], style: style, testID: testID, a11yProps: a11yProps }));
63
83
  }
64
- return (React.createElement(FullMotionRing, { pixelSize: pixelSize, strokeWidth: strokeWidth, arcColor: themeTokens["color-icon"], trackColor: themeTokens["color-surface--active"], style: style, testID: testID, a11yProps: a11yProps }));
84
+ return (React.createElement(FullMotionRing, { size: resolvedSize, pixelSize: pixelSize, strokeWidth: strokeWidth, arcColor: themeTokens["color-icon"], trackColor: themeTokens["color-surface--active"], style: style, testID: testID, a11yProps: a11yProps }));
65
85
  }
66
- function FullMotionRing({ pixelSize, strokeWidth, arcColor, trackColor, style, testID, a11yProps, }) {
86
+ function FullMotionRing({ size, pixelSize, strokeWidth, arcColor, trackColor, style, testID, a11yProps, }) {
87
+ const motion = MOTION_BY_SIZE[size];
67
88
  // Layer 1 — outer linear rotation. Continuous, constant speed.
68
89
  const outerRotate = useSharedValue(0);
69
90
  // Layer 2 — inner 8-phase rotation. Eight 135° eased segments per cycle
70
91
  // (totalling 1080° over `timing-indicator--cycle`) reproduce the
71
- // canonical Material Web "8-phase" rhythm.
92
+ // canonical Material Web "8-phase" rhythm. Only when motion.layers === 3.
72
93
  const innerRotate = useSharedValue(0);
73
94
  // Layer 3 — arc length and dash offset. Drives the visible arc growing
74
95
  // from ~1 viewBox-unit to ~90 viewBox-units and sliding around the ring.
96
+ // Only when motion.layers === 3; fixed-arc sizes use motion.dasharray.
75
97
  const arcProgress = useSharedValue(0);
76
98
  useEffect(() => {
77
99
  outerRotate.value = withRepeat(withTiming(360, {
78
- duration: tokens["timing-indicator--linear-rotate"],
100
+ duration: motion.linearRotateMs,
79
101
  easing: Easing.linear,
80
102
  }), -1);
103
+ if (motion.layers === 1) {
104
+ return;
105
+ }
81
106
  const phaseDuration = tokens["timing-indicator--cycle"] / 8;
82
107
  const phaseEasing = Easing.bezier(0.4, 0, 0.2, 1);
83
108
  innerRotate.value = withRepeat(withSequence(withTiming(135, { duration: phaseDuration, easing: phaseEasing }), withTiming(270, { duration: phaseDuration, easing: phaseEasing }), withTiming(405, { duration: phaseDuration, easing: phaseEasing }), withTiming(540, { duration: phaseDuration, easing: phaseEasing }), withTiming(675, { duration: phaseDuration, easing: phaseEasing }), withTiming(810, { duration: phaseDuration, easing: phaseEasing }), withTiming(945, { duration: phaseDuration, easing: phaseEasing }), withTiming(1080, { duration: phaseDuration, easing: phaseEasing })), -1);
@@ -87,7 +112,7 @@ function FullMotionRing({ pixelSize, strokeWidth, arcColor, trackColor, style, t
87
112
  }), -1);
88
113
  // We intentionally do not include the shared values in the dependency
89
114
  // array — they are stable references created by useSharedValue.
90
- }, []);
115
+ }, [motion]);
91
116
  const outerStyle = useAnimatedStyle(() => ({
92
117
  transform: [{ rotate: `${outerRotate.value}deg` }],
93
118
  }));
@@ -123,12 +148,17 @@ function FullMotionRing({ pixelSize, strokeWidth, arcColor, trackColor, style, t
123
148
  strokeDashoffset: offset,
124
149
  };
125
150
  });
151
+ const track = (React.createElement(Circle, { cx: CENTER, cy: CENTER, r: RADIUS, fill: "none", stroke: trackColor, strokeWidth: strokeWidth }));
152
+ const ring = motion.layers === 1 ? (
153
+ // Fixed-length arc, Layer 1 rotation only — no Layer 2 wrapper.
154
+ React.createElement(Svg, { width: pixelSize, height: pixelSize, viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}` },
155
+ track,
156
+ React.createElement(Circle, { cx: CENTER, cy: CENTER, r: RADIUS, fill: "none", stroke: arcColor, strokeWidth: strokeWidth, strokeLinecap: "round", strokeDasharray: motion.dasharray, strokeDashoffset: 0 }))) : (React.createElement(Animated.View, { style: [{ width: pixelSize, height: pixelSize }, innerStyle] },
157
+ React.createElement(Svg, { width: pixelSize, height: pixelSize, viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}` },
158
+ track,
159
+ React.createElement(AnimatedCircle, { cx: CENTER, cy: CENTER, r: RADIUS, fill: "none", stroke: arcColor, strokeWidth: strokeWidth, strokeLinecap: "round", animatedProps: arcAnimatedProps }))));
126
160
  return (React.createElement(Animated.View, Object.assign({ style: [{ justifyContent: "center", alignItems: "center" }, style] }, a11yProps, { testID: testID }),
127
- React.createElement(Animated.View, { style: [{ width: pixelSize, height: pixelSize }, outerStyle] },
128
- React.createElement(Animated.View, { style: [{ width: pixelSize, height: pixelSize }, innerStyle] },
129
- React.createElement(Svg, { width: pixelSize, height: pixelSize, viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}` },
130
- React.createElement(Circle, { cx: CENTER, cy: CENTER, r: RADIUS, fill: "none", stroke: trackColor, strokeWidth: strokeWidth }),
131
- React.createElement(AnimatedCircle, { cx: CENTER, cy: CENTER, r: RADIUS, fill: "none", stroke: arcColor, strokeWidth: strokeWidth, strokeLinecap: "round", animatedProps: arcAnimatedProps }))))));
161
+ React.createElement(Animated.View, { style: [{ width: pixelSize, height: pixelSize }, outerStyle] }, ring)));
132
162
  }
133
163
  function ReducedMotionRing({ pixelSize, strokeWidth, ringColor, style, testID, a11yProps, }) {
134
164
  // Reduced motion: hide rotation entirely, render one static ring in the
@@ -60,6 +60,41 @@ describe("ActivityIndicator", () => {
60
60
  expect(flattenedStyle.width).toBe(44);
61
61
  expect(flattenedStyle.height).toBe(44);
62
62
  });
63
+ it("uses a fixed-length arc for small (no Layer 2/3 wrappers)", () => {
64
+ const { getByTestId } = render(React.createElement(ActivityIndicator, { size: "small" }));
65
+ const outer = getByTestId(testId).children[0];
66
+ const outerChildren = React.Children.toArray(outer.props.children);
67
+ function typeName(node) {
68
+ var _a, _b;
69
+ if (!node || typeof node !== "object")
70
+ return "";
71
+ const element = node;
72
+ return ((_a = element.type) === null || _a === void 0 ? void 0 : _a.displayName) || ((_b = element.type) === null || _b === void 0 ? void 0 : _b.name) || "";
73
+ }
74
+ // Small: outer rotation wrapper → Svg directly (no inner Layer 2 view).
75
+ expect(outerChildren.some(child => typeName(child).startsWith("Svg"))).toBe(true);
76
+ function findArcCircle(node) {
77
+ var _a, _b;
78
+ if (!node || typeof node !== "object")
79
+ return undefined;
80
+ const element = node;
81
+ const name = typeName(element);
82
+ if ((name === "Circle" || name === "RNSVGCircle") &&
83
+ element.props.strokeDasharray === "40, 200") {
84
+ return element;
85
+ }
86
+ const kids = React.Children.toArray((_b = (_a = element.props) === null || _a === void 0 ? void 0 : _a.children) !== null && _b !== void 0 ? _b : []);
87
+ for (const kid of kids) {
88
+ const found = findArcCircle(kid);
89
+ if (found)
90
+ return found;
91
+ }
92
+ return undefined;
93
+ }
94
+ const arc = findArcCircle(outer);
95
+ expect(arc).toBeTruthy();
96
+ expect(arc === null || arc === void 0 ? void 0 : arc.props.strokeDashoffset).toBe(0);
97
+ });
63
98
  });
64
99
  describe('deprecated size="large" alias', () => {
65
100
  let warnSpy;
@@ -63,11 +63,12 @@ export function MediaView({ accessibilityLabel, showOverlay, showError, file, st
63
63
  handleMediaLoadEnd();
64
64
  setDecodeFailed(true);
65
65
  } },
66
- React.createElement(Overlay, { isLoading: isLoading, showOverlay: showOverlay, hasError: hasError, file: file, onUploadComplete: onUploadComplete, styles: styles }))));
66
+ React.createElement(Overlay, { isLoading: isLoading, showOverlay: showOverlay, hasError: hasError, file: file, onUploadComplete: onUploadComplete, styles: styles, styleInGrid: styleInGrid }))));
67
67
  }
68
- function Overlay({ isLoading, showOverlay, hasError, file, onUploadComplete, styles, }) {
69
- if (isLoading)
70
- return React.createElement(ActivityIndicator, null);
68
+ function Overlay({ isLoading, showOverlay, hasError, file, onUploadComplete, styles, styleInGrid, }) {
69
+ if (isLoading) {
70
+ return React.createElement(ActivityIndicator, { size: styleInGrid ? "small" : "base" });
71
+ }
71
72
  if (hasError)
72
73
  return React.createElement(ErrorOverlay, { styles: styles });
73
74
  if (showOverlay) {
@@ -97,11 +98,12 @@ function VideoPlaceholder({ a11yLabel, styleInGrid, surfaceColor, styles, isLoad
97
98
  surfaceColor ? { backgroundColor: surfaceColor } : undefined,
98
99
  ], testID: "format-file-video-placeholder" },
99
100
  file.showFileTypeIndicator && React.createElement(Icon, { name: "videoFile", size: "large" }),
100
- React.createElement(PlaceholderStatusOverlay, { isLoading: isLoading, showOverlay: showOverlay, hasError: hasError, file: file, onUploadComplete: onUploadComplete, styles: styles })));
101
+ React.createElement(PlaceholderStatusOverlay, { isLoading: isLoading, showOverlay: showOverlay, hasError: hasError, file: file, onUploadComplete: onUploadComplete, styles: styles, styleInGrid: styleInGrid })));
101
102
  }
102
- function PlaceholderStatusOverlay({ isLoading, showOverlay, hasError, file, onUploadComplete, styles, }) {
103
- if (isLoading)
104
- return React.createElement(ActivityIndicator, null);
103
+ function PlaceholderStatusOverlay({ isLoading, showOverlay, hasError, file, onUploadComplete, styles, styleInGrid, }) {
104
+ if (isLoading) {
105
+ return React.createElement(ActivityIndicator, { size: styleInGrid ? "small" : "base" });
106
+ }
105
107
  if (hasError)
106
108
  return React.createElement(ErrorOverlay, { styles: styles });
107
109
  if (showOverlay) {