@terreno/ui 0.15.0 → 0.15.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.
@@ -1,17 +1,18 @@
1
1
  import {Canvas, ImageFormat, Path, Skia, useCanvasRef} from "@shopify/react-native-skia";
2
2
  import {type FC, useCallback, useMemo, useRef, useState} from "react";
3
- import {Text, View} from "react-native";
3
+ import {Platform, Text, View} from "react-native";
4
4
  import {Gesture, GestureDetector} from "react-native-gesture-handler";
5
5
 
6
+ import {getSignaturePadHeight} from "./SignatureSizing";
6
7
  import {useTheme} from "./Theme";
7
8
 
8
9
  interface Props {
9
10
  onChange: (signature: string) => void;
10
11
  onStart?: () => void;
11
12
  onEnd?: () => void;
13
+ fullWidth?: boolean;
12
14
  }
13
15
 
14
- const SIGNATURE_PAD_HEIGHT_PX = 180;
15
16
  const STROKE_WIDTH_PX = 2.5;
16
17
  // Snapshot after the released stroke has painted to the Skia canvas.
17
18
  const SNAPSHOT_DELAY_MS = 60;
@@ -31,9 +32,10 @@ const SNAPSHOT_DELAY_MS = 60;
31
32
  * Reports the signature to the parent as a base64 PNG data URL via onChange,
32
33
  * and pushes "" on clear so "signature required" gating resets immediately.
33
34
  */
34
- export const Signature: FC<Props> = ({onChange, onStart, onEnd}: Props) => {
35
+ export const Signature: FC<Props> = ({fullWidth = false, onChange, onStart, onEnd}: Props) => {
35
36
  const {theme} = useTheme();
36
37
  const canvasRef = useCanvasRef();
38
+ const signaturePadHeight = getSignaturePadHeight(Platform.OS);
37
39
  const snapshotTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
38
40
  // Completed strokes as SVG path strings; the active stroke is tracked separately.
39
41
  const [completedStrokes, setCompletedStrokes] = useState<string[]>([]);
@@ -138,14 +140,14 @@ export const Signature: FC<Props> = ({onChange, onStart, onEnd}: Props) => {
138
140
  }, [clearSnapshotTimer, onChange]);
139
141
 
140
142
  return (
141
- <View style={{minWidth: 220}}>
143
+ <View style={{minWidth: 220, width: fullWidth ? "100%" : undefined}}>
142
144
  <GestureDetector gesture={panGesture}>
143
145
  <View
144
146
  style={{
145
147
  backgroundColor: theme.surface.base,
146
148
  borderColor: theme.border.dark,
147
149
  borderWidth: 1,
148
- height: SIGNATURE_PAD_HEIGHT_PX,
150
+ height: signaturePadHeight,
149
151
  overflow: "hidden",
150
152
  }}
151
153
  >
@@ -1,10 +1,51 @@
1
- import {describe, expect, it, mock} from "bun:test";
2
- import {fireEvent} from "@testing-library/react-native";
1
+ import {afterEach, describe, expect, it, mock} from "bun:test";
2
+ import {act, fireEvent} from "@testing-library/react-native";
3
+ import {View} from "react-native";
3
4
 
4
5
  import {Signature} from "./Signature";
5
6
  import {renderWithTheme} from "./test-utils";
6
7
 
8
+ const createMockContext = (): Record<string, ReturnType<typeof mock>> => ({
9
+ beginPath: mock(() => {}),
10
+ fillRect: mock(() => {}),
11
+ lineTo: mock(() => {}),
12
+ moveTo: mock(() => {}),
13
+ stroke: mock(() => {}),
14
+ });
15
+
16
+ const patchCanvasRefs = (
17
+ container: {
18
+ findAll: (
19
+ predicate: (node: {type: string}) => boolean
20
+ ) => Array<{props: Record<string, unknown>}>;
21
+ },
22
+ ctx: Record<string, ReturnType<typeof mock>>
23
+ ): HTMLCanvasElement => {
24
+ const canvasNodes = container.findAll((node: {type: string}) => node.type === "canvas");
25
+ const canvasNode = canvasNodes[0];
26
+ const canvas = {
27
+ getContext: mock(() => ctx),
28
+ height: 180,
29
+ releasePointerCapture: mock(() => {}),
30
+ setPointerCapture: mock(() => {}),
31
+ toDataURL: mock(() => "data:image/png;base64,AAAA"),
32
+ width: 300,
33
+ } as unknown as HTMLCanvasElement;
34
+
35
+ const ref = canvasNode.props.ref;
36
+ if (typeof ref === "function") {
37
+ ref(canvas);
38
+ } else if (ref && typeof ref === "object" && "current" in ref) {
39
+ (ref as {current: unknown}).current = canvas;
40
+ }
41
+ return canvas;
42
+ };
43
+
7
44
  describe("Signature", () => {
45
+ afterEach(() => {
46
+ mock.restore();
47
+ });
48
+
8
49
  it("renders correctly", () => {
9
50
  const mockOnChange = mock(() => {});
10
51
  const {toJSON} = renderWithTheme(<Signature onChange={mockOnChange} />);
@@ -22,12 +63,303 @@ describe("Signature", () => {
22
63
  expect(getByText("Clear")).toBeTruthy();
23
64
  });
24
65
 
66
+ it("scales the web canvas to the available container width", () => {
67
+ const mockOnChange = mock(() => {});
68
+ const {UNSAFE_getByType} = renderWithTheme(<Signature onChange={mockOnChange} />);
69
+ const canvas = UNSAFE_getByType("canvas");
70
+ expect(canvas.props.style).toMatchObject({maxWidth: "100%", width: "100%"});
71
+ });
72
+
73
+ it("allows the signature box to fill its parent", () => {
74
+ const mockOnChange = mock(() => {});
75
+ const {UNSAFE_getAllByType} = renderWithTheme(<Signature fullWidth onChange={mockOnChange} />);
76
+ const wrapper = UNSAFE_getAllByType(View)[1];
77
+ expect(wrapper.props.style).toMatchObject({maxWidth: undefined, width: "100%"});
78
+ });
79
+
80
+ it("maps scaled canvas pointer coordinates to the canvas buffer", () => {
81
+ const mockOnChange = mock(() => {});
82
+ const moveTo = mock(() => {});
83
+ const lineTo = mock(() => {});
84
+ const {UNSAFE_getByType} = renderWithTheme(<Signature onChange={mockOnChange} />);
85
+ const canvas = UNSAFE_getByType("canvas");
86
+ canvas.props.ref.current = {
87
+ clientHeight: 180,
88
+ clientWidth: 150,
89
+ getBoundingClientRect: () => ({height: 180, width: 150}),
90
+ getContext: () => ({
91
+ beginPath: mock(() => {}),
92
+ fillRect: mock(() => {}),
93
+ lineCap: "round",
94
+ lineJoin: "round",
95
+ lineTo,
96
+ moveTo,
97
+ stroke: mock(() => {}),
98
+ }),
99
+ height: 180,
100
+ setPointerCapture: mock(() => {}),
101
+ width: 300,
102
+ } as unknown as HTMLCanvasElement;
103
+
104
+ canvas.props.onPointerDown({
105
+ nativeEvent: {offsetX: 75, offsetY: 90},
106
+ pointerId: 1,
107
+ });
108
+ canvas.props.onPointerMove({
109
+ nativeEvent: {offsetX: 90, offsetY: 120},
110
+ pointerId: 1,
111
+ });
112
+
113
+ expect(moveTo).toHaveBeenCalledWith(150, 90);
114
+ expect(lineTo).toHaveBeenCalledWith(180, 120);
115
+ });
116
+
25
117
  it("notifies the parent with an empty value when Clear is pressed", () => {
26
118
  const mockOnChange = mock(() => {});
27
119
  const {getByText} = renderWithTheme(<Signature onChange={mockOnChange} />);
28
120
  fireEvent.press(getByText("Clear"));
29
- // Clearing the canvas emits no draw event, so the component must push ""
30
- // directly or "signature required" gating in parents would never reset.
31
121
  expect(mockOnChange).toHaveBeenCalledWith("");
32
122
  });
123
+
124
+ it("calls onStart when pointer down is fired on the canvas", () => {
125
+ const mockOnChange = mock(() => {});
126
+ const mockOnStart = mock(() => {});
127
+ const ctx = createMockContext();
128
+ const {UNSAFE_root} = renderWithTheme(
129
+ <Signature onChange={mockOnChange} onStart={mockOnStart} />
130
+ );
131
+
132
+ patchCanvasRefs(UNSAFE_root, ctx);
133
+
134
+ const canvasNodes = UNSAFE_root.findAll((node: {type: string}) => node.type === "canvas");
135
+ const canvasProps = canvasNodes[0].props as Record<string, (...args: unknown[]) => void>;
136
+
137
+ act(() => {
138
+ canvasProps.onPointerDown({
139
+ nativeEvent: {offsetX: 10, offsetY: 20},
140
+ pointerId: 1,
141
+ });
142
+ });
143
+
144
+ expect(mockOnStart).toHaveBeenCalledTimes(1);
145
+ expect(ctx.beginPath).toHaveBeenCalled();
146
+ expect(ctx.moveTo).toHaveBeenCalledWith(10, 20);
147
+ });
148
+
149
+ it("draws a stroke on pointerMove after pointerDown", () => {
150
+ const mockOnChange = mock(() => {});
151
+ const ctx = createMockContext();
152
+ const {UNSAFE_root} = renderWithTheme(<Signature onChange={mockOnChange} />);
153
+
154
+ patchCanvasRefs(UNSAFE_root, ctx);
155
+
156
+ const canvasNodes = UNSAFE_root.findAll((node: {type: string}) => node.type === "canvas");
157
+ const canvasProps = canvasNodes[0].props as Record<string, (...args: unknown[]) => void>;
158
+
159
+ act(() => {
160
+ canvasProps.onPointerDown({
161
+ nativeEvent: {offsetX: 5, offsetY: 5},
162
+ pointerId: 1,
163
+ });
164
+ });
165
+
166
+ act(() => {
167
+ canvasProps.onPointerMove({
168
+ nativeEvent: {offsetX: 50, offsetY: 50},
169
+ pointerId: 1,
170
+ });
171
+ });
172
+
173
+ expect(ctx.lineTo).toHaveBeenCalledWith(50, 50);
174
+ expect(ctx.stroke).toHaveBeenCalled();
175
+ });
176
+
177
+ it("ignores pointerMove when not drawing", () => {
178
+ const mockOnChange = mock(() => {});
179
+ const ctx = createMockContext();
180
+ const {UNSAFE_root} = renderWithTheme(<Signature onChange={mockOnChange} />);
181
+
182
+ patchCanvasRefs(UNSAFE_root, ctx);
183
+
184
+ const canvasNodes = UNSAFE_root.findAll((node: {type: string}) => node.type === "canvas");
185
+ const canvasProps = canvasNodes[0].props as Record<string, (...args: unknown[]) => void>;
186
+
187
+ act(() => {
188
+ canvasProps.onPointerMove({
189
+ nativeEvent: {offsetX: 50, offsetY: 50},
190
+ pointerId: 1,
191
+ });
192
+ });
193
+
194
+ expect(ctx.lineTo).not.toHaveBeenCalled();
195
+ });
196
+
197
+ it("exports the signature as a data URL on pointerUp after drawing", () => {
198
+ const mockOnChange = mock(() => {});
199
+ const mockOnEnd = mock(() => {});
200
+ const ctx = createMockContext();
201
+ const {UNSAFE_root} = renderWithTheme(<Signature onChange={mockOnChange} onEnd={mockOnEnd} />);
202
+
203
+ const canvas = patchCanvasRefs(UNSAFE_root, ctx);
204
+
205
+ const canvasNodes = UNSAFE_root.findAll((node: {type: string}) => node.type === "canvas");
206
+ const canvasProps = canvasNodes[0].props as Record<string, (...args: unknown[]) => void>;
207
+
208
+ // Start drawing
209
+ act(() => {
210
+ canvasProps.onPointerDown({
211
+ nativeEvent: {offsetX: 5, offsetY: 5},
212
+ pointerId: 1,
213
+ });
214
+ });
215
+
216
+ // Move to draw
217
+ act(() => {
218
+ canvasProps.onPointerMove({
219
+ nativeEvent: {offsetX: 50, offsetY: 50},
220
+ pointerId: 1,
221
+ });
222
+ });
223
+
224
+ // Lift the pointer
225
+ act(() => {
226
+ canvasProps.onPointerUp({
227
+ nativeEvent: {offsetX: 50, offsetY: 50},
228
+ pointerId: 1,
229
+ });
230
+ });
231
+
232
+ expect(mockOnChange).toHaveBeenCalledWith("data:image/png;base64,AAAA");
233
+ expect(mockOnEnd).toHaveBeenCalledTimes(1);
234
+ expect(
235
+ (canvas as unknown as {releasePointerCapture: ReturnType<typeof mock>}).releasePointerCapture
236
+ ).toHaveBeenCalledWith(1);
237
+ });
238
+
239
+ it("does not call onChange on pointerUp if nothing was drawn", () => {
240
+ const mockOnChange = mock(() => {});
241
+ const ctx = createMockContext();
242
+ const {UNSAFE_root} = renderWithTheme(<Signature onChange={mockOnChange} />);
243
+
244
+ patchCanvasRefs(UNSAFE_root, ctx);
245
+
246
+ const canvasNodes = UNSAFE_root.findAll((node: {type: string}) => node.type === "canvas");
247
+ const canvasProps = canvasNodes[0].props as Record<string, (...args: unknown[]) => void>;
248
+
249
+ // pointerDown without any move (no drawing)
250
+ act(() => {
251
+ canvasProps.onPointerDown({
252
+ nativeEvent: {offsetX: 5, offsetY: 5},
253
+ pointerId: 1,
254
+ });
255
+ });
256
+
257
+ act(() => {
258
+ canvasProps.onPointerUp({
259
+ nativeEvent: {offsetX: 5, offsetY: 5},
260
+ pointerId: 1,
261
+ });
262
+ });
263
+
264
+ // onChange should not be called with a data URL (no strokes were made)
265
+ expect(mockOnChange).not.toHaveBeenCalled();
266
+ });
267
+
268
+ it("ignores pointerUp when not drawing", () => {
269
+ const mockOnChange = mock(() => {});
270
+ const mockOnEnd = mock(() => {});
271
+ const ctx = createMockContext();
272
+ const {UNSAFE_root} = renderWithTheme(<Signature onChange={mockOnChange} onEnd={mockOnEnd} />);
273
+
274
+ patchCanvasRefs(UNSAFE_root, ctx);
275
+
276
+ const canvasNodes = UNSAFE_root.findAll((node: {type: string}) => node.type === "canvas");
277
+ const canvasProps = canvasNodes[0].props as Record<string, (...args: unknown[]) => void>;
278
+
279
+ act(() => {
280
+ canvasProps.onPointerUp({
281
+ nativeEvent: {offsetX: 50, offsetY: 50},
282
+ pointerId: 1,
283
+ });
284
+ });
285
+
286
+ expect(mockOnEnd).not.toHaveBeenCalled();
287
+ expect(mockOnChange).not.toHaveBeenCalled();
288
+ });
289
+
290
+ it("handles pointerLeave the same as pointerUp", () => {
291
+ const mockOnChange = mock(() => {});
292
+ const mockOnEnd = mock(() => {});
293
+ const ctx = createMockContext();
294
+ const {UNSAFE_root} = renderWithTheme(<Signature onChange={mockOnChange} onEnd={mockOnEnd} />);
295
+
296
+ patchCanvasRefs(UNSAFE_root, ctx);
297
+
298
+ const canvasNodes = UNSAFE_root.findAll((node: {type: string}) => node.type === "canvas");
299
+ const canvasProps = canvasNodes[0].props as Record<string, (...args: unknown[]) => void>;
300
+
301
+ // Draw something
302
+ act(() => {
303
+ canvasProps.onPointerDown({
304
+ nativeEvent: {offsetX: 5, offsetY: 5},
305
+ pointerId: 1,
306
+ });
307
+ });
308
+ act(() => {
309
+ canvasProps.onPointerMove({
310
+ nativeEvent: {offsetX: 50, offsetY: 50},
311
+ pointerId: 1,
312
+ });
313
+ });
314
+
315
+ // Leave triggers pointerUp handler
316
+ act(() => {
317
+ canvasProps.onPointerLeave({
318
+ nativeEvent: {offsetX: 50, offsetY: 50},
319
+ pointerId: 1,
320
+ });
321
+ });
322
+
323
+ expect(mockOnChange).toHaveBeenCalledWith("data:image/png;base64,AAAA");
324
+ expect(mockOnEnd).toHaveBeenCalledTimes(1);
325
+ });
326
+
327
+ it("resets canvas and notifies parent on clear after drawing", () => {
328
+ const mockOnChange = mock(() => {});
329
+ const ctx = createMockContext();
330
+ const {UNSAFE_root, getByText} = renderWithTheme(<Signature onChange={mockOnChange} />);
331
+
332
+ patchCanvasRefs(UNSAFE_root, ctx);
333
+
334
+ const canvasNodes = UNSAFE_root.findAll((node: {type: string}) => node.type === "canvas");
335
+ const canvasProps = canvasNodes[0].props as Record<string, (...args: unknown[]) => void>;
336
+
337
+ // Draw
338
+ act(() => {
339
+ canvasProps.onPointerDown({
340
+ nativeEvent: {offsetX: 5, offsetY: 5},
341
+ pointerId: 1,
342
+ });
343
+ });
344
+ act(() => {
345
+ canvasProps.onPointerMove({
346
+ nativeEvent: {offsetX: 50, offsetY: 50},
347
+ pointerId: 1,
348
+ });
349
+ });
350
+ act(() => {
351
+ canvasProps.onPointerUp({
352
+ nativeEvent: {offsetX: 50, offsetY: 50},
353
+ pointerId: 1,
354
+ });
355
+ });
356
+
357
+ mockOnChange.mockClear();
358
+
359
+ // Clear
360
+ fireEvent.press(getByText("Clear"));
361
+
362
+ expect(mockOnChange).toHaveBeenCalledWith("");
363
+ expect(ctx.fillRect).toHaveBeenCalled();
364
+ });
33
365
  });
package/src/Signature.tsx CHANGED
@@ -13,6 +13,7 @@ export interface SignatureProps {
13
13
  onChange: (signature: string) => void;
14
14
  onStart?: () => void;
15
15
  onEnd?: () => void;
16
+ fullWidth?: boolean;
16
17
  value?: string; // note this
17
18
  }
18
19
 
@@ -20,6 +21,13 @@ const SIGNATURE_WIDTH_PX = 300;
20
21
  const SIGNATURE_HEIGHT_PX = 180;
21
22
  const STROKE_WIDTH_PX = 2.5;
22
23
 
24
+ interface CanvasPoint {
25
+ x: number;
26
+ y: number;
27
+ }
28
+
29
+ type DrawingEvent = ReactPointerEvent<HTMLCanvasElement>;
30
+
23
31
  /**
24
32
  * Web signature pad backed by a raw HTML5 <canvas> — no third-party library.
25
33
  *
@@ -27,7 +35,12 @@ const STROKE_WIDTH_PX = 2.5;
27
35
  * data URL via onChange. Clearing pushes "" so "signature required" gating in
28
36
  * parents resets immediately, since clearing the canvas emits no draw event.
29
37
  */
30
- export const Signature = ({onChange, onStart, onEnd}: SignatureProps): ReactElement => {
38
+ export const Signature = ({
39
+ fullWidth = false,
40
+ onChange,
41
+ onStart,
42
+ onEnd,
43
+ }: SignatureProps): ReactElement => {
31
44
  const {theme} = useTheme();
32
45
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
33
46
  const isDrawingRef = useRef(false);
@@ -41,6 +54,28 @@ export const Signature = ({onChange, onStart, onEnd}: SignatureProps): ReactElem
41
54
  return canvas.getContext("2d");
42
55
  }, []);
43
56
 
57
+ const getCanvasPoint = useCallback((event: DrawingEvent): CanvasPoint | null => {
58
+ const canvas = canvasRef.current;
59
+ if (!canvas) {
60
+ return null;
61
+ }
62
+
63
+ const rect = canvas.getBoundingClientRect?.();
64
+ const displayWidth = rect?.width ?? canvas.clientWidth ?? canvas.width;
65
+ const displayHeight = rect?.height ?? canvas.clientHeight ?? canvas.height;
66
+ if (displayWidth <= 0 || displayHeight <= 0) {
67
+ return {
68
+ x: event.nativeEvent.offsetX,
69
+ y: event.nativeEvent.offsetY,
70
+ };
71
+ }
72
+
73
+ return {
74
+ x: (event.nativeEvent.offsetX / displayWidth) * canvas.width,
75
+ y: (event.nativeEvent.offsetY / displayHeight) * canvas.height,
76
+ };
77
+ }, []);
78
+
44
79
  /**
45
80
  * Paints the opaque background and configures stroke styling. Re-runs when
46
81
  * the theme changes so the pad matches the active light/dark colors.
@@ -65,38 +100,40 @@ export const Signature = ({onChange, onStart, onEnd}: SignatureProps): ReactElem
65
100
  }, [resetCanvas]);
66
101
 
67
102
  const handlePointerDown = useCallback(
68
- (event: ReactPointerEvent<HTMLCanvasElement>): void => {
103
+ (event: DrawingEvent): void => {
69
104
  const ctx = getContext();
70
- if (!ctx) {
105
+ const point = getCanvasPoint(event);
106
+ if (!ctx || !point) {
71
107
  return;
72
108
  }
73
109
  canvasRef.current?.setPointerCapture?.(event.pointerId);
74
110
  isDrawingRef.current = true;
75
111
  ctx.beginPath();
76
- ctx.moveTo(event.nativeEvent.offsetX, event.nativeEvent.offsetY);
112
+ ctx.moveTo(point.x, point.y);
77
113
  onStart?.();
78
114
  },
79
- [getContext, onStart]
115
+ [getCanvasPoint, getContext, onStart]
80
116
  );
81
117
 
82
118
  const handlePointerMove = useCallback(
83
- (event: ReactPointerEvent<HTMLCanvasElement>): void => {
119
+ (event: DrawingEvent): void => {
84
120
  if (!isDrawingRef.current) {
85
121
  return;
86
122
  }
87
123
  const ctx = getContext();
88
- if (!ctx) {
124
+ const point = getCanvasPoint(event);
125
+ if (!ctx || !point) {
89
126
  return;
90
127
  }
91
- ctx.lineTo(event.nativeEvent.offsetX, event.nativeEvent.offsetY);
128
+ ctx.lineTo(point.x, point.y);
92
129
  ctx.stroke();
93
130
  hasDrawnRef.current = true;
94
131
  },
95
- [getContext]
132
+ [getCanvasPoint, getContext]
96
133
  );
97
134
 
98
135
  const handlePointerUp = useCallback(
99
- (event: ReactPointerEvent<HTMLCanvasElement>): void => {
136
+ (event: DrawingEvent): void => {
100
137
  if (!isDrawingRef.current) {
101
138
  return;
102
139
  }
@@ -126,7 +163,7 @@ export const Signature = ({onChange, onStart, onEnd}: SignatureProps): ReactElem
126
163
  style={{
127
164
  borderColor: theme.border.dark,
128
165
  borderWidth: 1,
129
- maxWidth: SIGNATURE_WIDTH_PX,
166
+ maxWidth: fullWidth ? undefined : SIGNATURE_WIDTH_PX,
130
167
  width: "100%",
131
168
  }}
132
169
  >
@@ -137,7 +174,13 @@ export const Signature = ({onChange, onStart, onEnd}: SignatureProps): ReactElem
137
174
  onPointerMove={handlePointerMove}
138
175
  onPointerUp={handlePointerUp}
139
176
  ref={canvasRef}
140
- style={{height: SIGNATURE_HEIGHT_PX, touchAction: "none", width: SIGNATURE_WIDTH_PX}}
177
+ style={{
178
+ display: "block",
179
+ height: SIGNATURE_HEIGHT_PX,
180
+ maxWidth: "100%",
181
+ touchAction: "none",
182
+ width: "100%",
183
+ }}
141
184
  width={SIGNATURE_WIDTH_PX}
142
185
  />
143
186
  </View>
@@ -20,6 +20,7 @@ export const SignatureField = ({
20
20
  onEnd,
21
21
  disabledText,
22
22
  errorText,
23
+ fullWidth = false,
23
24
  }: SignatureFieldProps): ReactElement => {
24
25
  const {theme} = useTheme();
25
26
  if (disabled) {
@@ -62,6 +63,7 @@ export const SignatureField = ({
62
63
  )}
63
64
  <View style={{marginVertical: 8}}>
64
65
  <Signature
66
+ fullWidth={fullWidth}
65
67
  onChange={onChange}
66
68
  onEnd={() => {
67
69
  onEnd?.();
@@ -0,0 +1,10 @@
1
+ export const SIGNATURE_PAD_HEIGHT_PX = 180;
2
+ export const IOS_SIGNATURE_PAD_HEIGHT_PX = 120;
3
+
4
+ export const getSignaturePadHeight = (platformOS: string): number => {
5
+ if (platformOS === "ios") {
6
+ return IOS_SIGNATURE_PAD_HEIGHT_PX;
7
+ }
8
+
9
+ return SIGNATURE_PAD_HEIGHT_PX;
10
+ };
package/src/Toast.tsx CHANGED
@@ -11,15 +11,17 @@ import {isAPIError, printAPIError} from "./Utilities";
11
11
 
12
12
  const TOAST_DURATION_MS = 3 * 1000;
13
13
 
14
- type UseToastVariantOptions = {
14
+ interface UseToastVariantOptions {
15
15
  persistent?: ToastProps["persistent"];
16
16
  secondary?: ToastProps["secondary"];
17
17
  size?: ToastProps["size"];
18
18
  onDismiss?: ToastProps["onDismiss"];
19
19
  subtitle?: ToastProps["subtitle"];
20
- };
20
+ }
21
21
 
22
- type UseToastOptions = {variant?: ToastProps["variant"]} & UseToastVariantOptions;
22
+ interface UseToastOptions extends UseToastVariantOptions {
23
+ variant?: ToastProps["variant"];
24
+ }
23
25
 
24
26
  export const useToast = (): {
25
27
  hide: (id: string) => void;
@@ -8,7 +8,9 @@ exports[`DismissButton renders correctly with default props 1`] = `
8
8
  "$$typeof": Symbol(react.test.json),
9
9
  "children": null,
10
10
  "props": {
11
- "style": undefined,
11
+ "onPointerEnter": [Function: AsyncFunction],
12
+ "onPointerLeave": [Function: AsyncFunction],
13
+ "style": {},
12
14
  "testID": undefined,
13
15
  },
14
16
  "type": "View",
@@ -38,7 +40,9 @@ exports[`DismissButton renders with primary color (default) 1`] = `
38
40
  "$$typeof": Symbol(react.test.json),
39
41
  "children": null,
40
42
  "props": {
41
- "style": undefined,
43
+ "onPointerEnter": [Function: AsyncFunction],
44
+ "onPointerLeave": [Function: AsyncFunction],
45
+ "style": {},
42
46
  "testID": undefined,
43
47
  },
44
48
  "type": "View",
@@ -68,7 +72,9 @@ exports[`DismissButton renders with inverted color 1`] = `
68
72
  "$$typeof": Symbol(react.test.json),
69
73
  "children": null,
70
74
  "props": {
71
- "style": undefined,
75
+ "onPointerEnter": [Function: AsyncFunction],
76
+ "onPointerLeave": [Function: AsyncFunction],
77
+ "style": {},
72
78
  "testID": undefined,
73
79
  },
74
80
  "type": "View",
@@ -4991,9 +4991,11 @@ exports[`Field renders signature field 1`] = `
4991
4991
  "current": null,
4992
4992
  },
4993
4993
  "style": {
4994
+ "display": "block",
4994
4995
  "height": 180,
4996
+ "maxWidth": "100%",
4995
4997
  "touchAction": "none",
4996
- "width": 300,
4998
+ "width": "100%",
4997
4999
  },
4998
5000
  "width": 300,
4999
5001
  },
@@ -20,9 +20,11 @@ exports[`Signature renders correctly 1`] = `
20
20
  "current": null,
21
21
  },
22
22
  "style": {
23
+ "display": "block",
23
24
  "height": 180,
25
+ "maxWidth": "100%",
24
26
  "touchAction": "none",
25
- "width": 300,
27
+ "width": "100%",
26
28
  },
27
29
  "width": 300,
28
30
  },
@@ -42,9 +42,11 @@ exports[`SignatureField renders correctly with default props 1`] = `
42
42
  "current": null,
43
43
  },
44
44
  "style": {
45
+ "display": "block",
45
46
  "height": 180,
47
+ "maxWidth": "100%",
46
48
  "touchAction": "none",
47
- "width": 300,
49
+ "width": "100%",
48
50
  },
49
51
  "width": 300,
50
52
  },