@jobber/components-native 0.105.4 → 0.106.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.
Files changed (38) hide show
  1. package/dist/docs/ActionItem/ActionItem.md +1 -1
  2. package/dist/docs/ButtonGroup/ButtonGroup.md +2 -2
  3. package/dist/docs/Chip/Chip.md +4 -4
  4. package/dist/docs/EmptyState/EmptyState.md +1 -1
  5. package/dist/docs/Icon/Icon.md +1 -1
  6. package/dist/docs/IconButton/IconButton.md +1 -1
  7. package/dist/docs/usage-guidelines/usage-guidelines.md +5 -5
  8. package/dist/package.json +5 -3
  9. package/dist/src/primitives/Portal/index.js +5 -0
  10. package/dist/src/primitives/Select/SelectPrimitive.js +173 -0
  11. package/dist/src/primitives/Select/SelectPrimitive.style.js +112 -0
  12. package/dist/src/primitives/Select/SelectPrimitive.test.js +243 -0
  13. package/dist/src/primitives/Select/index.js +1 -0
  14. package/dist/src/primitives/Select/types.js +1 -0
  15. package/dist/src/primitives/index.js +2 -0
  16. package/dist/src/utils/test/PortalHostWrapper.js +11 -0
  17. package/dist/src/utils/test/index.js +1 -0
  18. package/dist/tsconfig.build.tsbuildinfo +1 -1
  19. package/dist/types/src/primitives/Portal/index.d.ts +1 -0
  20. package/dist/types/src/primitives/Select/SelectPrimitive.d.ts +67 -0
  21. package/dist/types/src/primitives/Select/SelectPrimitive.style.d.ts +105 -0
  22. package/dist/types/src/primitives/Select/SelectPrimitive.test.d.ts +1 -0
  23. package/dist/types/src/primitives/Select/index.d.ts +2 -0
  24. package/dist/types/src/primitives/Select/types.d.ts +41 -0
  25. package/dist/types/src/primitives/index.d.ts +2 -0
  26. package/dist/types/src/utils/test/PortalHostWrapper.d.ts +10 -0
  27. package/dist/types/src/utils/test/index.d.ts +1 -0
  28. package/package.json +5 -3
  29. package/src/primitives/Portal/index.ts +5 -0
  30. package/src/primitives/Select/SelectPrimitive.stories.tsx +221 -0
  31. package/src/primitives/Select/SelectPrimitive.style.ts +141 -0
  32. package/src/primitives/Select/SelectPrimitive.test.tsx +372 -0
  33. package/src/primitives/Select/SelectPrimitive.tsx +292 -0
  34. package/src/primitives/Select/index.ts +17 -0
  35. package/src/primitives/Select/types.ts +96 -0
  36. package/src/primitives/index.ts +2 -0
  37. package/src/utils/test/PortalHostWrapper.tsx +19 -0
  38. package/src/utils/test/index.ts +1 -0
@@ -0,0 +1,243 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import React from "react";
11
+ import { render, screen, userEvent, within, } from "@testing-library/react-native";
12
+ import { SelectPrimitive } from "./SelectPrimitive";
13
+ import { tokens } from "../../utils/design";
14
+ import { PortalHostWrapper } from "../../utils/test";
15
+ import { Icon } from "../../Icon";
16
+ const user = userEvent.setup();
17
+ beforeAll(() => {
18
+ var _a;
19
+ const MockNativeMethods = require("react-native/jest/MockNativeMethods");
20
+ const mockedMethods = (_a = MockNativeMethods.default) !== null && _a !== void 0 ? _a : MockNativeMethods;
21
+ mockedMethods.measure.mockImplementation((callback) => {
22
+ callback(0, 0, 320, 48, 20, 100);
23
+ });
24
+ });
25
+ function renderSelect({ defaultValue, onOpenChange, disabled, invalid, testID, triggerRef, } = {}) {
26
+ return render(React.createElement(SelectPrimitive.Root, { defaultValue: defaultValue, onOpenChange: onOpenChange, disabled: disabled },
27
+ React.createElement(SelectPrimitive.Trigger, { ref: triggerRef, testID: testID, invalid: invalid },
28
+ React.createElement(SelectPrimitive.Value, { placeholder: "Select an option" }))));
29
+ }
30
+ describe("SelectPrimitive.Trigger", () => {
31
+ it("renders with the default testID", () => {
32
+ renderSelect();
33
+ expect(screen.getByTestId("ATL-Select-Trigger")).toBeDefined();
34
+ });
35
+ it("namespaces a provided testID", () => {
36
+ renderSelect({ testID: "city" });
37
+ expect(screen.getByTestId("ATL-city-Select-Trigger")).toBeDefined();
38
+ expect(screen.queryByTestId("ATL-Select-Trigger")).toBeNull();
39
+ });
40
+ it("fires onOpenChange and applies open styling when pressed", () => __awaiter(void 0, void 0, void 0, function* () {
41
+ const onOpenChange = jest.fn();
42
+ renderSelect({ onOpenChange });
43
+ const trigger = screen.getByTestId("ATL-Select-Trigger");
44
+ expect(trigger).toHaveStyle({
45
+ borderColor: tokens["color-border--interactive"],
46
+ });
47
+ yield user.press(trigger);
48
+ expect(onOpenChange).toHaveBeenCalledWith(true);
49
+ expect(trigger).toHaveStyle({
50
+ borderColor: tokens["color-interactive--subtle"],
51
+ borderWidth: tokens["border-thick"],
52
+ });
53
+ }));
54
+ it("does not open when disabled", () => __awaiter(void 0, void 0, void 0, function* () {
55
+ const onOpenChange = jest.fn();
56
+ renderSelect({ onOpenChange, disabled: true });
57
+ yield user.press(screen.getByTestId("ATL-Select-Trigger"));
58
+ expect(onOpenChange).not.toHaveBeenCalled();
59
+ }));
60
+ it("applies disabled styling: subtle fill and disabled value text", () => {
61
+ renderSelect({ disabled: true });
62
+ expect(screen.getByTestId("ATL-Select-Trigger")).toHaveStyle({
63
+ backgroundColor: tokens["color-disabled--secondary"],
64
+ });
65
+ expect(screen.getByText("Select an option")).toHaveStyle({
66
+ color: tokens["color-disabled"],
67
+ });
68
+ });
69
+ it("applies critical border styling when invalid", () => {
70
+ renderSelect({ invalid: true });
71
+ expect(screen.getByTestId("ATL-Select-Trigger")).toHaveStyle({
72
+ borderColor: tokens["color-critical"],
73
+ });
74
+ });
75
+ it("exposes a combobox role and conveys disabled to the accessibility state", () => {
76
+ renderSelect({ disabled: true });
77
+ const trigger = screen.getByRole("combobox");
78
+ expect(trigger.props.accessibilityState).toMatchObject({ disabled: true });
79
+ });
80
+ it("announces the selected option as the trigger's accessibility value", () => {
81
+ renderSelect({ defaultValue: { value: "tor", label: "Toronto" } });
82
+ const trigger = screen.getByTestId("ATL-Select-Trigger");
83
+ expect(trigger.props.accessibilityValue).toEqual({ text: "Toronto" });
84
+ });
85
+ });
86
+ describe("SelectPrimitive.Trigger ref", () => {
87
+ it("forwards a ref exposing the trigger with open/close", () => {
88
+ var _a, _b;
89
+ const triggerRef = React.createRef();
90
+ renderSelect({ triggerRef });
91
+ expect(triggerRef.current).not.toBeNull();
92
+ expect(typeof ((_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.open)).toBe("function");
93
+ expect(typeof ((_b = triggerRef.current) === null || _b === void 0 ? void 0 : _b.close)).toBe("function");
94
+ });
95
+ });
96
+ const CITIES = [
97
+ { value: "tor", label: "Toronto" },
98
+ { value: "van", label: "Vancouver" },
99
+ { value: "edm", label: "Edmonton" },
100
+ ];
101
+ function renderSelectWithContent({ defaultValue, onValueChange, closeOnPress, disabledItem, } = {}) {
102
+ return render(React.createElement(SelectPrimitive.Root, { defaultValue: defaultValue, onValueChange: onValueChange },
103
+ React.createElement(SelectPrimitive.Trigger, null,
104
+ React.createElement(SelectPrimitive.Value, { placeholder: "Select an option" })),
105
+ React.createElement(SelectPrimitive.Portal, null,
106
+ React.createElement(SelectPrimitive.Overlay, null),
107
+ React.createElement(SelectPrimitive.Content, null,
108
+ React.createElement(SelectPrimitive.Viewport, null,
109
+ React.createElement(SelectPrimitive.Group, null,
110
+ React.createElement(SelectPrimitive.Label, null, "Cities"),
111
+ CITIES.map(city => (React.createElement(SelectPrimitive.Item, { key: city.value, value: city.value, label: city.label, closeOnPress: closeOnPress, disabled: disabledItem === city.value },
112
+ React.createElement(SelectPrimitive.ItemText, { disabled: disabledItem === city.value }),
113
+ React.createElement(SelectPrimitive.ItemIndicator, null,
114
+ React.createElement(Icon, { name: "checkmark" })))))),
115
+ React.createElement(SelectPrimitive.Separator, null))))), { wrapper: PortalHostWrapper });
116
+ }
117
+ function openSelect() {
118
+ return __awaiter(this, void 0, void 0, function* () {
119
+ yield user.press(screen.getByTestId("ATL-Select-Trigger"));
120
+ });
121
+ }
122
+ describe("SelectPrimitive.Content", () => {
123
+ it("renders nothing until the trigger opens it", () => __awaiter(void 0, void 0, void 0, function* () {
124
+ renderSelectWithContent();
125
+ expect(screen.queryByText("Toronto")).toBeNull();
126
+ yield openSelect();
127
+ expect(screen.getByText("Toronto")).toBeDefined();
128
+ expect(screen.getByText("Vancouver")).toBeDefined();
129
+ expect(screen.getByText("Cities")).toBeDefined();
130
+ }));
131
+ it("dismisses when tapping outside (overlay), without selecting", () => __awaiter(void 0, void 0, void 0, function* () {
132
+ const onValueChange = jest.fn();
133
+ renderSelectWithContent({ onValueChange });
134
+ yield openSelect();
135
+ yield user.press(screen.getByTestId("ATL-Select-Overlay"));
136
+ expect(screen.queryByText("Toronto")).toBeNull();
137
+ expect(onValueChange).not.toHaveBeenCalled();
138
+ }));
139
+ });
140
+ describe("SelectPrimitive.Item", () => {
141
+ it("selects the option, emits { value, label }, and closes", () => __awaiter(void 0, void 0, void 0, function* () {
142
+ const onValueChange = jest.fn();
143
+ renderSelectWithContent({ onValueChange });
144
+ yield openSelect();
145
+ yield user.press(screen.getByText("Vancouver"));
146
+ expect(onValueChange).toHaveBeenCalledWith({
147
+ value: "van",
148
+ label: "Vancouver",
149
+ });
150
+ // Dropdown closed; trigger now shows the selection.
151
+ expect(screen.queryByText("Cities")).toBeNull();
152
+ expect(screen.getByText("Vancouver")).toBeDefined();
153
+ }));
154
+ it("passes closeOnPress through — dropdown stays open when false", () => __awaiter(void 0, void 0, void 0, function* () {
155
+ const onValueChange = jest.fn();
156
+ renderSelectWithContent({ onValueChange, closeOnPress: false });
157
+ yield openSelect();
158
+ yield user.press(screen.getByText("Vancouver"));
159
+ expect(onValueChange).toHaveBeenCalled();
160
+ expect(screen.getByText("Cities")).toBeDefined();
161
+ }));
162
+ it("does not select a disabled option", () => __awaiter(void 0, void 0, void 0, function* () {
163
+ const onValueChange = jest.fn();
164
+ renderSelectWithContent({ onValueChange, disabledItem: "van" });
165
+ yield openSelect();
166
+ yield user.press(screen.getByText("Vancouver"));
167
+ expect(onValueChange).not.toHaveBeenCalled();
168
+ }));
169
+ it("greys a disabled option's text when ItemText is told it is disabled", () => __awaiter(void 0, void 0, void 0, function* () {
170
+ renderSelectWithContent({ disabledItem: "van" });
171
+ yield openSelect();
172
+ expect(screen.getByText("Vancouver")).toHaveStyle({
173
+ color: tokens["color-disabled"],
174
+ });
175
+ expect(screen.getByText("Toronto")).toHaveStyle({
176
+ color: tokens["color-text"],
177
+ });
178
+ }));
179
+ it("shows the checkmark indicator only on the selected option", () => __awaiter(void 0, void 0, void 0, function* () {
180
+ renderSelectWithContent({
181
+ defaultValue: { value: "tor", label: "Toronto" },
182
+ });
183
+ yield openSelect();
184
+ // One checkmark icon in the dropdown (the selected row's indicator).
185
+ expect(screen.getAllByTestId("checkmark")).toHaveLength(1);
186
+ }));
187
+ it("renders whatever marker is composed into ItemIndicator on the selected row", () => __awaiter(void 0, void 0, void 0, function* () {
188
+ render(React.createElement(SelectPrimitive.Root, { defaultValue: { value: "tor", label: "Toronto" } },
189
+ React.createElement(SelectPrimitive.Trigger, null,
190
+ React.createElement(SelectPrimitive.Value, { placeholder: "Select an option" })),
191
+ React.createElement(SelectPrimitive.Portal, null,
192
+ React.createElement(SelectPrimitive.Overlay, null),
193
+ React.createElement(SelectPrimitive.Content, null,
194
+ React.createElement(SelectPrimitive.Viewport, null,
195
+ React.createElement(SelectPrimitive.Item, { value: "tor", label: "Toronto" },
196
+ React.createElement(SelectPrimitive.ItemText, null),
197
+ React.createElement(SelectPrimitive.ItemIndicator, null,
198
+ React.createElement(Icon, { name: "person" }))))))), { wrapper: PortalHostWrapper });
199
+ yield openSelect();
200
+ expect(screen.getByTestId("person")).toBeDefined();
201
+ expect(screen.queryByTestId("checkmark")).toBeNull();
202
+ }));
203
+ it("conveys option role plus selected and disabled states to assistive tech", () => __awaiter(void 0, void 0, void 0, function* () {
204
+ var _a, _b;
205
+ renderSelectWithContent({
206
+ defaultValue: { value: "tor", label: "Toronto" },
207
+ disabledItem: "van",
208
+ });
209
+ yield openSelect();
210
+ const options = screen.getAllByRole("option");
211
+ const optionWith = (label) => options.find(option => within(option).queryByText(label));
212
+ expect((_a = optionWith("Toronto")) === null || _a === void 0 ? void 0 : _a.props.accessibilityState).toMatchObject({
213
+ checked: true,
214
+ });
215
+ expect((_b = optionWith("Vancouver")) === null || _b === void 0 ? void 0 : _b.props.accessibilityState).toMatchObject({
216
+ disabled: true,
217
+ });
218
+ }));
219
+ });
220
+ describe("SelectPrimitive.Value", () => {
221
+ it("shows the placeholder with subdued styling when nothing is selected", () => {
222
+ renderSelect();
223
+ const placeholder = screen.getByText("Select an option");
224
+ expect(placeholder).toHaveStyle({
225
+ color: tokens["color-text--secondary"],
226
+ });
227
+ });
228
+ it("shows the selected option's label", () => {
229
+ renderSelect({ defaultValue: { value: "tor", label: "Toronto" } });
230
+ const value = screen.getByText("Toronto");
231
+ expect(value).toHaveStyle({ color: tokens["color-text"] });
232
+ expect(screen.queryByText("Select an option")).toBeNull();
233
+ });
234
+ it("greys the value when the field is disabled (set on Root)", () => {
235
+ renderSelect({
236
+ disabled: true,
237
+ defaultValue: { value: "tor", label: "Toronto" },
238
+ });
239
+ expect(screen.getByText("Toronto")).toHaveStyle({
240
+ color: tokens["color-disabled"],
241
+ });
242
+ });
243
+ });
@@ -0,0 +1 @@
1
+ export { SelectPrimitive } from "./SelectPrimitive";
@@ -0,0 +1 @@
1
+ export {};
@@ -2,3 +2,5 @@
2
2
  * This file exports styled components-native primitives.
3
3
  */
4
4
  export * from "./HelperText";
5
+ export * from "./Portal";
6
+ export * from "./Select";
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { PortalHost } from "@rn-primitives/portal";
3
+ /**
4
+ * Test wrapper that mounts a `PortalHost` so portal-based overlays (e.g. the
5
+ * Select dropdown) render: `render(ui, { wrapper: PortalHostWrapper })`.
6
+ */
7
+ export function PortalHostWrapper({ children }) {
8
+ return (React.createElement(React.Fragment, null,
9
+ children,
10
+ React.createElement(PortalHost, null)));
11
+ }
@@ -1 +1,2 @@
1
1
  export { MockSafeAreaProvider } from "./MockSafeAreaProvider";
2
+ export { PortalHostWrapper } from "./PortalHostWrapper";