@jobber/components-native 0.105.4 → 0.105.5-contentove-4cd673f.10
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/docs/Chip/Chip.md +4 -4
- package/dist/docs/ContentOverlay/ContentOverlay.md +1 -0
- package/dist/docs/usage-guidelines/usage-guidelines.md +5 -5
- package/dist/package.json +2 -2
- package/dist/src/ContentOverlay/ContentOverlay.js +21 -7
- package/dist/src/ContentOverlay/ContentOverlay.test.js +35 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/ContentOverlay/ContentOverlay.d.ts +1 -1
- package/dist/types/src/ContentOverlay/types.d.ts +12 -0
- package/package.json +2 -2
- package/src/ContentOverlay/ContentOverlay.stories.tsx +15 -1
- package/src/ContentOverlay/ContentOverlay.test.tsx +56 -0
- package/src/ContentOverlay/ContentOverlay.tsx +48 -7
- package/src/ContentOverlay/docs/ContentOverlayWithFooter.tsx +85 -0
- package/src/ContentOverlay/docs/index.ts +1 -0
- package/src/ContentOverlay/types.ts +12 -0
package/dist/docs/Chip/Chip.md
CHANGED
|
@@ -4,7 +4,7 @@ Chip is a flexible component that can be used for
|
|
|
4
4
|
|
|
5
5
|
* inline single- or multi-selection of items
|
|
6
6
|
* triggering filtering and selection components like
|
|
7
|
-
[
|
|
7
|
+
[FilterPicker](/components/FilterPicker)
|
|
8
8
|
* presenting grouped items that can be added or removed
|
|
9
9
|
|
|
10
10
|
```tsx
|
|
@@ -236,8 +236,8 @@ export function ChipInvalidExample(
|
|
|
236
236
|
|
|
237
237
|
* [Chips](/components/Chips) is a convenience wrapper that offers the
|
|
238
238
|
single-select, multi-select, and add/dismiss functionality "out of the box"
|
|
239
|
-
* [
|
|
240
|
-
a separate component
|
|
239
|
+
* [FilterPicker](/components/FilterPicker) is most commonly triggered by a Chip,
|
|
240
|
+
but is a separate component
|
|
241
241
|
* [Select](../Select/Select.md) is a simpler single-select "dropdown" that
|
|
242
242
|
presents as a form element and should be preferred in forms
|
|
243
243
|
* [RadioGroup](/components/RadioGroup) should be used to allow the user to
|
|
@@ -256,7 +256,7 @@ ideally 1–2 words. If any of the options in the group may have longer labels,
|
|
|
256
256
|
consider Checkbox or Radio as necessary for your selection type.
|
|
257
257
|
|
|
258
258
|
In cases where a Chip displays name of its selections, such as when used to
|
|
259
|
-
trigger a
|
|
259
|
+
trigger a FilterPicker or a date range selector, use the heading to identify the
|
|
260
260
|
"category" and the label to identify the selected items.
|
|
261
261
|
|
|
262
262
|
## Accessibility
|
|
@@ -50,6 +50,7 @@ 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
|
+
| `footerComponent` | `ReactNode` | No | `undefined` | Content pinned to the bottom of the overlay, above the keyboard — e.g. a persistent search input (results scroll abov... |
|
|
53
54
|
| `fullScreen` | `boolean` | No | `false` | Force overlay height to fill the screen. Width not impacted. |
|
|
54
55
|
| `isDraggable` | `boolean` | No | `true` | If false, hides the handle and turns off dragging. |
|
|
55
56
|
| `keyboardShouldPersistTaps` | `boolean` | No | `false` | Allows taps to be registered behind keyboard if enabled |
|
|
@@ -17,10 +17,10 @@ deprecated.
|
|
|
17
17
|
|
|
18
18
|
## Deprecated components — do not use
|
|
19
19
|
|
|
20
|
-
| Deprecated | Replacement
|
|
21
|
-
| --------------------- |
|
|
22
|
-
| `MultiSelect` | `Autocomplete` with `multiple` prop, or `
|
|
23
|
-
| `Chips` (dismissible) | `Autocomplete` with `multiple` prop
|
|
20
|
+
| Deprecated | Replacement | Notes |
|
|
21
|
+
| --------------------- | ------------------------------------------------------ | ---------------------------------------- |
|
|
22
|
+
| `MultiSelect` | `Autocomplete` with `multiple` prop, or `FilterPicker` | Fully deprecated, no longer supported. |
|
|
23
|
+
| `Chips` (dismissible) | `Autocomplete` with `multiple` prop | Dismissible Chips variant is deprecated. |
|
|
24
24
|
|
|
25
25
|
### Deprecated props on still-supported components
|
|
26
26
|
|
|
@@ -77,7 +77,7 @@ parent reads its compound children to wire up state, accessibility, and styling.
|
|
|
77
77
|
| `Modal` v2 | `Modal.Provider`, `Modal.Activator`, `Modal.Content`, `Modal.Header`, `Modal.Actions` | Wrap with `Modal.Provider`. `Modal.Header` and `Modal.Actions` must be **direct children** of `Modal.Content` for sticky behavior to work. Use `Modal.Activator` to control focus return when the trigger element unmounts. |
|
|
78
78
|
| `Banner` | `Banner.Provider`, `Banner.Content`, `Banner.Icon`, `Banner.DismissButton`, `Banner.Action` | Either the simple `<Banner type="...">children</Banner>` form, or the Provider form: `Banner.Provider` > `Banner.Content` (+ optional `Banner.Icon` / `Banner.Action` / `Banner.DismissButton`). |
|
|
79
79
|
| `Card` | `Card.Header`, `Card.Body` | `Card` > `Card.Header` + `Card.Body`. The legacy `header` string/element prop still works for simple cases. Use composition for any header that needs richer markup. |
|
|
80
|
-
| `
|
|
80
|
+
| `FilterPicker` | `FilterPicker.Activator`, `FilterPicker.Option`, `FilterPicker.Action` | `FilterPicker` > optional `FilterPicker.Activator`, one or more `FilterPicker.Option`, optional `FilterPicker.Action`. Omitting `FilterPicker.Activator` renders the default activator. |
|
|
81
81
|
| `DataTable` | `DataTable.Container`, `DataTable.Table`, `DataTable.Header`, `DataTable.HeaderCell`, `DataTable.SortableHeader`, `DataTable.Body`, `DataTable.Row`, `DataTable.Cell`, `DataTable.RowActions`, `DataTable.Actions`, `DataTable.Footer`, `DataTable.Pagination`, `DataTable.PaginationButton` | `DataTable.Footer` is a sibling of `DataTable.Body`, not a child. Place pagination outside the table (not inside the footer). For `layout="fixed"`, pass explicit widths via `style={{ width: "..." }}` on each `HeaderCell`. |
|
|
82
82
|
| `Menu` (web composable) | `Menu.Trigger`, `Menu.Content`, `Menu.Section`, `Menu.Header`, `Menu.HeaderLabel`, `Menu.Item`, `Menu.ItemIcon`, `Menu.ItemLabel`, `Menu.Separator` | `Menu` > `Menu.Trigger` + `Menu.Content` containing `Menu.Item`s (optionally grouped in `Menu.Section`s). Each `Menu.Item` must include `textValue` for type-ahead and screen readers. |
|
|
83
83
|
| `Page` | `Page.Header`, `Page.HeaderContent`, `Page.TitleBar`, `Page.Title`, `Page.Subtitle`, `Page.Intro`, `Page.Actions`, `Page.ActionPrimary`, `Page.ActionSecondary`, `Page.ActionMenu`, `Page.PrimaryButton`, `Page.SecondaryButton`, `Page.Menu`, `Page.Body` | Composable `Page` API: `Page` > `Page.Header` (containing `Page.HeaderContent` with `Page.TitleBar` > `Page.Title` / `Page.Subtitle`, and `Page.Actions` with `Page.ActionPrimary` / `Page.ActionSecondary` / `Page.ActionMenu`) + `Page.Body`. The legacy props-based API (`title`, `subtitle`, `intro`, etc.) still works. |
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.105.
|
|
3
|
+
"version": "0.105.5-contentove-4cd673f.10+4cd673ff",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native implementation of Atlantis",
|
|
6
6
|
"repository": {
|
|
@@ -122,5 +122,5 @@
|
|
|
122
122
|
"react-native-screens": ">=4.18.0",
|
|
123
123
|
"react-native-svg": ">=12.0.0"
|
|
124
124
|
},
|
|
125
|
-
"gitHead": "
|
|
125
|
+
"gitHead": "4cd673ff20841cf9e6ea375cf0577310ffaae365"
|
|
126
126
|
}
|
|
@@ -12,11 +12,12 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import React, { createContext, useContext, useImperativeHandle, useMemo, useRef, useState, } from "react";
|
|
13
13
|
import { AccessibilityInfo, Platform, View, findNodeHandle, useWindowDimensions, } from "react-native";
|
|
14
14
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
15
|
-
import { BottomSheetBackdrop, BottomSheetModal, BottomSheetView, } from "@gorhom/bottom-sheet";
|
|
15
|
+
import { BottomSheetBackdrop, BottomSheetModal, BottomSheetScrollView, BottomSheetView, } from "@gorhom/bottom-sheet";
|
|
16
16
|
import { FullWindowOverlay } from "react-native-screens";
|
|
17
17
|
import { BottomSheetKeyboardAwareScrollView } from "./BottomSheetKeyboardAwareScrollView";
|
|
18
18
|
import { useStyles } from "./ContentOverlay.style";
|
|
19
19
|
import { useBottomSheetModalBackHandler } from "./hooks/useBottomSheetModalBackHandler";
|
|
20
|
+
import { useKeyboardVisibility } from "./hooks/useKeyboardVisibility";
|
|
20
21
|
import { computeContentOverlayBehavior } from "./computeContentOverlayBehavior";
|
|
21
22
|
import { KEYBOARD_TOP_PADDING_AUTO_SCROLL } from "./constants";
|
|
22
23
|
import { useIsScreenReaderEnabled } from "../hooks";
|
|
@@ -46,8 +47,9 @@ function getModalBackgroundColor(variation, tokens) {
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
// 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, }) {
|
|
50
|
+
export function ContentOverlay({ children, title, accessibilityLabel, fullScreen = false, showDismiss = false, isDraggable = true, adjustToContentHeight = false, keyboardShouldPersistTaps = false, scrollEnabled = false, modalBackgroundColor = "surface", footerComponent, onClose, onOpen, onBeforeExit, loading = false, ref, }) {
|
|
50
51
|
const insets = useSafeAreaInsets();
|
|
52
|
+
const { isKeyboardVisible } = useKeyboardVisibility();
|
|
51
53
|
const { width: windowWidth } = useWindowDimensions();
|
|
52
54
|
const bottomSheetModalRef = useRef(null);
|
|
53
55
|
const previousIndexRef = useRef(-1);
|
|
@@ -68,18 +70,22 @@ export function ContentOverlay({ children, title, accessibilityLabel, fullScreen
|
|
|
68
70
|
});
|
|
69
71
|
const effectiveIsDraggable = behavior.isDraggable;
|
|
70
72
|
const shouldShowDismiss = behavior.showDismiss;
|
|
73
|
+
const hasHeader = Boolean(title || shouldShowDismiss);
|
|
74
|
+
const stickyHeaderIndices = hasHeader ? [0] : undefined;
|
|
71
75
|
const isCloseableOnOverlayTap = onBeforeExit === undefined;
|
|
72
76
|
// Prevent the Overlay from being flush with the top of the screen, even if we are "100%" or "fullscreen"
|
|
73
77
|
const topInset = insets.top || tokens["space-larger"];
|
|
74
78
|
const [showHeaderShadow, setShowHeaderShadow] = useState(false);
|
|
75
79
|
const overlayHeader = useRef(null);
|
|
76
80
|
const scrollViewRef = useRef(null);
|
|
81
|
+
const hasFooter = footerComponent !== undefined;
|
|
77
82
|
// enableDynamicSizing will add another snap point of the content height
|
|
78
83
|
const snapPoints = useMemo(() => {
|
|
79
|
-
// There is a bug with "restore" behavior after keyboard is dismissed.
|
|
84
|
+
// There is a bug with "restore" behavior after the keyboard is dismissed.
|
|
80
85
|
// https://github.com/gorhom/react-native-bottom-sheet/issues/2465
|
|
81
|
-
//
|
|
82
|
-
//
|
|
86
|
+
// Providing a 100% snap point "fixes" it for now. The upstream fix
|
|
87
|
+
// (https://github.com/gorhom/react-native-bottom-sheet/pull/2511) was
|
|
88
|
+
// closed without merging, so there is no released fix to wait for.
|
|
83
89
|
return ["100%"];
|
|
84
90
|
}, []);
|
|
85
91
|
const onCloseController = () => {
|
|
@@ -174,8 +180,16 @@ export function ContentOverlay({ children, title, accessibilityLabel, fullScreen
|
|
|
174
180
|
const backdropComponent = useMemo(() => function ContentOverlayBackdrop(props) {
|
|
175
181
|
return (React.createElement(Backdrop, Object.assign({}, props, { pressBehavior: isCloseableOnOverlayTap ? "close" : "none" })));
|
|
176
182
|
}, [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() },
|
|
178
|
-
React.createElement(ContentOverlayKeyboardContext.Provider, { value: scrollEnabled },
|
|
183
|
+
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 || scrollEnabled, enableHandlePanningGesture: effectiveIsDraggable, enableDynamicSizing: behavior.initialHeight === "contentHeight", keyboardBehavior: "interactive", keyboardBlurBehavior: "restore", topInset: topInset, onDismiss: () => onClose === null || onClose === void 0 ? void 0 : onClose() },
|
|
184
|
+
React.createElement(ContentOverlayKeyboardContext.Provider, { value: scrollEnabled && !hasFooter }, hasFooter ? (React.createElement(View, { style: { flex: 1 } },
|
|
185
|
+
scrollEnabled ? (React.createElement(BottomSheetScrollView, { ref: scrollViewRef, style: { flex: 1 }, keyboardShouldPersistTaps: keyboardShouldPersistTaps ? "handled" : "never", showsVerticalScrollIndicator: false, onScroll: handleOnScroll, stickyHeaderIndices: stickyHeaderIndices },
|
|
186
|
+
renderHeader(),
|
|
187
|
+
React.createElement(View, { testID: "ATL-Overlay-Children" }, children))) : (React.createElement(BottomSheetView, { style: { flex: 1 } },
|
|
188
|
+
renderHeader(),
|
|
189
|
+
React.createElement(View, { testID: "ATL-Overlay-Children" }, children))),
|
|
190
|
+
React.createElement(View, { style: {
|
|
191
|
+
paddingBottom: isKeyboardVisible ? 0 : insets.bottom,
|
|
192
|
+
} }, footerComponent))) : scrollEnabled ? (React.createElement(BottomSheetKeyboardAwareScrollView, { ref: scrollViewRef, contentContainerStyle: { paddingBottom: insets.bottom }, keyboardShouldPersistTaps: keyboardShouldPersistTaps ? "handled" : "never", showsVerticalScrollIndicator: false, onScroll: handleOnScroll, stickyHeaderIndices: stickyHeaderIndices, bottomOffset: KEYBOARD_TOP_PADDING_AUTO_SCROLL },
|
|
179
193
|
renderHeader(),
|
|
180
194
|
React.createElement(View, { testID: "ATL-Overlay-Children" }, children))) : (React.createElement(BottomSheetView, null,
|
|
181
195
|
renderHeader(),
|
|
@@ -295,3 +295,38 @@ describe("title prop", () => {
|
|
|
295
295
|
}));
|
|
296
296
|
});
|
|
297
297
|
});
|
|
298
|
+
describe("when a footerComponent is provided", () => {
|
|
299
|
+
function renderWithFooter() {
|
|
300
|
+
const contentOverlayRef = createRef();
|
|
301
|
+
render(React.createElement(AtlantisOverlayProvider, null,
|
|
302
|
+
React.createElement(View, null,
|
|
303
|
+
React.createElement(Button, { label: "Open Content Overlay", onPress: () => { var _a, _b; return (_b = (_a = contentOverlayRef.current) === null || _a === void 0 ? void 0 : _a.open) === null || _b === void 0 ? void 0 : _b.call(_a); } }),
|
|
304
|
+
React.createElement(ContentOverlay, { ref: contentOverlayRef, adjustToContentHeight: true, scrollEnabled: true, onOpen: jest.fn(), footerComponent: React.createElement(Text, null, "Search team") },
|
|
305
|
+
React.createElement(Content, null,
|
|
306
|
+
React.createElement(Text, null, "Marty Fayes"))))));
|
|
307
|
+
return contentOverlayRef;
|
|
308
|
+
}
|
|
309
|
+
function openWithFooter() {
|
|
310
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
311
|
+
jest.useFakeTimers();
|
|
312
|
+
renderWithFooter();
|
|
313
|
+
yield user.press(screen.getByLabelText("Open Content Overlay"));
|
|
314
|
+
yield act(() => __awaiter(this, void 0, void 0, function* () {
|
|
315
|
+
jest.runAllTimers();
|
|
316
|
+
}));
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
it("renders the footer content alongside the body content", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
320
|
+
yield openWithFooter();
|
|
321
|
+
yield waitFor(() => {
|
|
322
|
+
expect(screen.getByText("Search team")).toBeDefined();
|
|
323
|
+
expect(screen.getByText("Marty Fayes")).toBeDefined();
|
|
324
|
+
});
|
|
325
|
+
}));
|
|
326
|
+
it("still exposes the overlay body content container", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
327
|
+
yield openWithFooter();
|
|
328
|
+
yield waitFor(() => {
|
|
329
|
+
expect(screen.getByTestId("ATL-Overlay-Children")).toBeDefined();
|
|
330
|
+
});
|
|
331
|
+
}));
|
|
332
|
+
});
|