@jobber/components-native 0.107.2 → 0.108.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.
@@ -50,6 +50,9 @@ where it will be a maximum width of 640px and be centered on the screen.
50
50
  | `children` | `ReactNode` | Yes | — | Content to be passed into the overlay |
51
51
  | `accessibilityLabel` | `string` | No | `"Close {title} modal"` | Optional accessibilityLabel describing the overlay. This will read out when the overlay is opened. |
52
52
  | `adjustToContentHeight` | `boolean` | No | `false` | If true, automatically adjusts the overlay height to the content height. This will disable the ability to drag the ov... |
53
+ | `allowDragWithBeforeExit` | `boolean` | No | `false` | When `true`, `onBeforeExit` intercepts the dismiss button and back press but does not disable drag. Drag-to-dismiss b... |
54
+ | `enableContentPanningGesture` | `boolean` | No | — | Whether pan on the content area drags the sheet. Set `false` to let nested scrollables own the gesture. Defaults to t... |
55
+ | `enablePanDownToClose` | `boolean` | No | — | Whether panning down on the content area closes the sheet. Set false to prevent the sheet from being closed by pannin... |
53
56
  | `fullScreen` | `boolean` | No | `false` | Force overlay height to fill the screen. Width not impacted. |
54
57
  | `isDraggable` | `boolean` | No | `true` | If false, hides the handle and turns off dragging. |
55
58
  | `keyboardShouldPersistTaps` | `boolean` | No | `false` | Allows taps to be registered behind keyboard if enabled |
@@ -61,4 +64,5 @@ where it will be a maximum width of 640px and be centered on the screen.
61
64
  | `ref` | `Ref<{ open?: () => void; close?: () => void; }>` | No | — | Ref to the content overlay component. |
62
65
  | `scrollEnabled` | `boolean` | No | `false` | Enables scrolling in the content body of overlay |
63
66
  | `showDismiss` | `boolean` | No | `false` | Display the dismiss button in the header of the overlay. |
67
+ | `snapPoints` | `(string | number)[]` | No | — | Explicit snap points for the overlay (percentages or pixel values). When provided, `adjustToContentHeight` is ignored... |
64
68
  | `title` | `string` | No | — | Title of overlay, appears in the header next to the close button. |
@@ -417,6 +417,7 @@ export function IconSizesExample() {
417
417
  | | `future` |
418
418
  | | `history` |
419
419
  | | `import` |
420
+ | | `merge` |
420
421
  | | `redo` |
421
422
  | | `remove` |
422
423
  | | `search` |
@@ -21,6 +21,7 @@ for the “emptiness” may include, but are not limited to…
21
21
  * Search results with no match
22
22
  * An error has resulted in the user not being able to access content they
23
23
  otherwise could
24
+ * A specific field doesn't have a value (for example, a custom field that the user hasn't populated)
24
25
 
25
26
  ## Solution
26
27
 
@@ -63,6 +64,34 @@ a single Banner can look out of place.
63
64
  </Box>
64
65
  ```
65
66
 
67
+ ### Field-level empty states
68
+
69
+ When a field doesn't have a value, use a dash to indicate the lack of a value.
70
+ This provides:
71
+
72
+ * confirmation that nothing was entered
73
+ * confirmation that something *can* be entered there should the information require an update
74
+
75
+ ```tsx
76
+ <Card>
77
+ <Content>
78
+ <Heading level={4}>Additional details</Heading>
79
+ <Stack gap="smallest">
80
+ <Text size="small" variation="subdued">Preferred service date</Text>
81
+ <Text>June 25, 2026</Text>
82
+ </Stack>
83
+ <Stack gap="smallest">
84
+ <Text size="small" variation="subdued">Gate code</Text>
85
+ <Text>—</Text>
86
+ </Stack>
87
+ <Stack gap="smallest">
88
+ <Text size="small" variation="subdued">Additional requests</Text>
89
+ <Text>—</Text>
90
+ </Stack>
91
+ </Content>
92
+ </Card>
93
+ ```
94
+
66
95
  ### When the user can't add content
67
96
 
68
97
  When a card is empty, and there is no way for the user to add content, you
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.107.2",
3
+ "version": "0.108.0",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -73,7 +73,7 @@
73
73
  "devDependencies": {
74
74
  "@babel/runtime": "^7.29.2",
75
75
  "@gorhom/bottom-sheet": "^5.2.8",
76
- "@jobber/design": "0.106.1",
76
+ "@jobber/design": "0.107.0",
77
77
  "@jobber/hooks": "2.21.0",
78
78
  "@react-native-community/datetimepicker": "^8.4.5",
79
79
  "@react-native/babel-preset": "^0.82.1",
@@ -124,5 +124,5 @@
124
124
  "react-native-screens": ">=4.18.0",
125
125
  "react-native-svg": ">=12.0.0"
126
126
  },
127
- "gitHead": "3f6678b1792101e43e544ec51cfa17d883362460"
127
+ "gitHead": "d5470a957047c13206da81b769b223f08e670d92"
128
128
  }
@@ -46,7 +46,7 @@ function getModalBackgroundColor(variation, tokens) {
46
46
  }
47
47
  }
48
48
  // eslint-disable-next-line max-statements
49
- export function ContentOverlay({ children, title, accessibilityLabel, fullScreen = false, showDismiss = false, isDraggable = true, adjustToContentHeight = false, keyboardShouldPersistTaps = false, scrollEnabled = false, modalBackgroundColor = "surface", onClose, onOpen, onBeforeExit, loading = false, ref, }) {
49
+ export function ContentOverlay({ children, title, accessibilityLabel, fullScreen = false, showDismiss = false, isDraggable = true, adjustToContentHeight = false, keyboardShouldPersistTaps = false, scrollEnabled = false, modalBackgroundColor = "surface", onClose, onOpen, onBeforeExit, allowDragWithBeforeExit = false, enablePanDownToClose, enableContentPanningGesture, snapPoints: customSnapPoints, loading = false, ref, }) {
50
50
  const insets = useSafeAreaInsets();
51
51
  const { width: windowWidth } = useWindowDimensions();
52
52
  const bottomSheetModalRef = useRef(null);
@@ -61,6 +61,7 @@ export function ContentOverlay({ children, title, accessibilityLabel, fullScreen
61
61
  adjustToContentHeight,
62
62
  isDraggable,
63
63
  hasOnBeforeExit: onBeforeExit !== undefined,
64
+ allowDragWithBeforeExit,
64
65
  showDismiss,
65
66
  }, {
66
67
  isScreenReaderEnabled,
@@ -76,12 +77,15 @@ export function ContentOverlay({ children, title, accessibilityLabel, fullScreen
76
77
  const scrollViewRef = useRef(null);
77
78
  // enableDynamicSizing will add another snap point of the content height
78
79
  const snapPoints = useMemo(() => {
80
+ if (customSnapPoints && customSnapPoints.length > 0) {
81
+ return customSnapPoints;
82
+ }
79
83
  // There is a bug with "restore" behavior after keyboard is dismissed.
80
84
  // https://github.com/gorhom/react-native-bottom-sheet/issues/2465
81
85
  // providing a 100% snap point "fixes" it for now, but there is an approved PR to fix it
82
86
  // that just needs to be merged and released: https://github.com/gorhom/react-native-bottom-sheet/pull/2511
83
87
  return ["100%"];
84
- }, []);
88
+ }, [customSnapPoints]);
85
89
  const onCloseController = () => {
86
90
  var _a;
87
91
  if (!onBeforeExit) {
@@ -174,7 +178,7 @@ export function ContentOverlay({ children, title, accessibilityLabel, fullScreen
174
178
  const backdropComponent = useMemo(() => function ContentOverlayBackdrop(props) {
175
179
  return (React.createElement(Backdrop, Object.assign({}, props, { pressBehavior: isCloseableOnOverlayTap ? "close" : "none" })));
176
180
  }, [isCloseableOnOverlayTap]);
177
- return (React.createElement(BottomSheetModal, { ref: bottomSheetModalRef, containerComponent: Platform.OS === "ios" ? Container : undefined, onChange: handleChange, style: sheetStyle, backgroundStyle: backgroundStyle, handleStyle: styles.handleWrapper, handleIndicatorStyle: handleIndicatorStyles, backdropComponent: backdropComponent, snapPoints: snapPoints, enablePanDownToClose: effectiveIsDraggable, enableContentPanningGesture: effectiveIsDraggable, enableHandlePanningGesture: effectiveIsDraggable, enableDynamicSizing: behavior.initialHeight === "contentHeight", keyboardBehavior: "interactive", keyboardBlurBehavior: "restore", topInset: topInset, onDismiss: () => onClose === null || onClose === void 0 ? void 0 : onClose() },
181
+ return (React.createElement(BottomSheetModal, { ref: bottomSheetModalRef, containerComponent: Platform.OS === "ios" ? Container : undefined, onChange: handleChange, style: sheetStyle, backgroundStyle: backgroundStyle, handleStyle: styles.handleWrapper, handleIndicatorStyle: handleIndicatorStyles, backdropComponent: backdropComponent, snapPoints: snapPoints, enablePanDownToClose: enablePanDownToClose !== null && enablePanDownToClose !== void 0 ? enablePanDownToClose : effectiveIsDraggable, enableContentPanningGesture: enableContentPanningGesture !== null && enableContentPanningGesture !== void 0 ? enableContentPanningGesture : effectiveIsDraggable, enableHandlePanningGesture: effectiveIsDraggable, enableDynamicSizing: !customSnapPoints && behavior.initialHeight === "contentHeight", keyboardBehavior: "interactive", keyboardBlurBehavior: "restore", topInset: topInset, onDismiss: () => onClose === null || onClose === void 0 ? void 0 : onClose() },
178
182
  React.createElement(ContentOverlayKeyboardContext.Provider, { value: scrollEnabled }, scrollEnabled ? (React.createElement(BottomSheetKeyboardAwareScrollView, { ref: scrollViewRef, contentContainerStyle: { paddingBottom: insets.bottom }, keyboardShouldPersistTaps: keyboardShouldPersistTaps ? "handled" : "never", showsVerticalScrollIndicator: false, onScroll: handleOnScroll, stickyHeaderIndices: [0], bottomOffset: KEYBOARD_TOP_PADDING_AUTO_SCROLL },
179
183
  renderHeader(),
180
184
  React.createElement(View, { testID: "ATL-Overlay-Children" }, children))) : (React.createElement(BottomSheetView, null,
@@ -42,14 +42,17 @@ function computeInitialHeight(config, isDraggable) {
42
42
  }
43
43
  /**
44
44
  * Draggability determination:
45
- * - hasOnBeforeExit: true → false (silent override, regardless of isDraggable prop)
45
+ * - hasOnBeforeExit: true && !allowDragWithBeforeExit → false (legacy override)
46
+ * - hasOnBeforeExit: true && allowDragWithBeforeExit → use isDraggable prop value
46
47
  * - Otherwise → use isDraggable prop value
47
48
  *
48
- * This silent override exists because onBeforeExit needs to intercept close attempts,
49
- * and dragging would bypass that interception.
49
+ * The legacy override exists because onBeforeExit needs to intercept close
50
+ * attempts, and dragging would bypass that interception. Consumers that
51
+ * explicitly accept the asymmetry (e.g. button-press confirmation,
52
+ * drag-dismiss silent) can opt out via `allowDragWithBeforeExit`.
50
53
  */
51
54
  function computeIsDraggable(config) {
52
- if (config.hasOnBeforeExit) {
55
+ if (config.hasOnBeforeExit && !config.allowDragWithBeforeExit) {
53
56
  return false;
54
57
  }
55
58
  return config.isDraggable;
@@ -5,6 +5,7 @@ const defaultConfig = {
5
5
  adjustToContentHeight: false,
6
6
  isDraggable: true,
7
7
  hasOnBeforeExit: false,
8
+ allowDragWithBeforeExit: false,
8
9
  showDismiss: false,
9
10
  };
10
11
  const defaultState = {
@@ -69,6 +70,36 @@ describe("computeContentOverlayBehavior", () => {
69
70
  const result = computeContentOverlayBehavior(config, state);
70
71
  expect(result.isDraggable).toBe(false);
71
72
  });
73
+ it("respects isDraggable=true when onBeforeExit is present and allowDragWithBeforeExit=true", () => {
74
+ const config = aConfig({
75
+ isDraggable: true,
76
+ hasOnBeforeExit: true,
77
+ allowDragWithBeforeExit: true,
78
+ });
79
+ const state = aState();
80
+ const result = computeContentOverlayBehavior(config, state);
81
+ expect(result.isDraggable).toBe(true);
82
+ });
83
+ it("respects isDraggable=false when onBeforeExit is present and allowDragWithBeforeExit=true", () => {
84
+ const config = aConfig({
85
+ isDraggable: false,
86
+ hasOnBeforeExit: true,
87
+ allowDragWithBeforeExit: true,
88
+ });
89
+ const state = aState();
90
+ const result = computeContentOverlayBehavior(config, state);
91
+ expect(result.isDraggable).toBe(false);
92
+ });
93
+ it("still overrides to false when onBeforeExit is present and allowDragWithBeforeExit=false (default)", () => {
94
+ const config = aConfig({
95
+ isDraggable: true,
96
+ hasOnBeforeExit: true,
97
+ allowDragWithBeforeExit: false,
98
+ });
99
+ const state = aState();
100
+ const result = computeContentOverlayBehavior(config, state);
101
+ expect(result.isDraggable).toBe(false);
102
+ });
72
103
  });
73
104
  describe("showDismiss", () => {
74
105
  it("returns true when showDismiss prop is true", () => {