@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,372 @@
1
+ import React from "react";
2
+ import {
3
+ render,
4
+ screen,
5
+ userEvent,
6
+ within,
7
+ } from "@testing-library/react-native";
8
+ import type { TriggerRef } from "@rn-primitives/select";
9
+ import { SelectPrimitive } from "./SelectPrimitive";
10
+ import { tokens } from "../../utils/design";
11
+ import { PortalHostWrapper } from "../../utils/test";
12
+ import { Icon } from "../../Icon";
13
+
14
+ const user = userEvent.setup();
15
+
16
+ beforeAll(() => {
17
+ const MockNativeMethods = require("react-native/jest/MockNativeMethods");
18
+ const mockedMethods = MockNativeMethods.default ?? MockNativeMethods;
19
+ mockedMethods.measure.mockImplementation(
20
+ (
21
+ callback: (
22
+ x: number,
23
+ y: number,
24
+ width: number,
25
+ height: number,
26
+ pageX: number,
27
+ pageY: number,
28
+ ) => void,
29
+ ) => {
30
+ callback(0, 0, 320, 48, 20, 100);
31
+ },
32
+ );
33
+ });
34
+
35
+ function renderSelect({
36
+ defaultValue,
37
+ onOpenChange,
38
+ disabled,
39
+ invalid,
40
+ testID,
41
+ triggerRef,
42
+ }: {
43
+ defaultValue?: { value: string; label: string };
44
+ onOpenChange?: (open: boolean) => void;
45
+ disabled?: boolean;
46
+ invalid?: boolean;
47
+ testID?: string;
48
+ triggerRef?: React.Ref<TriggerRef>;
49
+ } = {}) {
50
+ return render(
51
+ <SelectPrimitive.Root
52
+ defaultValue={defaultValue}
53
+ onOpenChange={onOpenChange}
54
+ disabled={disabled}
55
+ >
56
+ <SelectPrimitive.Trigger
57
+ ref={triggerRef}
58
+ testID={testID}
59
+ invalid={invalid}
60
+ >
61
+ <SelectPrimitive.Value placeholder="Select an option" />
62
+ </SelectPrimitive.Trigger>
63
+ </SelectPrimitive.Root>,
64
+ );
65
+ }
66
+
67
+ describe("SelectPrimitive.Trigger", () => {
68
+ it("renders with the default testID", () => {
69
+ renderSelect();
70
+ expect(screen.getByTestId("ATL-Select-Trigger")).toBeDefined();
71
+ });
72
+
73
+ it("namespaces a provided testID", () => {
74
+ renderSelect({ testID: "city" });
75
+ expect(screen.getByTestId("ATL-city-Select-Trigger")).toBeDefined();
76
+ expect(screen.queryByTestId("ATL-Select-Trigger")).toBeNull();
77
+ });
78
+
79
+ it("fires onOpenChange and applies open styling when pressed", async () => {
80
+ const onOpenChange = jest.fn();
81
+ renderSelect({ onOpenChange });
82
+
83
+ const trigger = screen.getByTestId("ATL-Select-Trigger");
84
+ expect(trigger).toHaveStyle({
85
+ borderColor: tokens["color-border--interactive"],
86
+ });
87
+
88
+ await user.press(trigger);
89
+
90
+ expect(onOpenChange).toHaveBeenCalledWith(true);
91
+ expect(trigger).toHaveStyle({
92
+ borderColor: tokens["color-interactive--subtle"],
93
+ borderWidth: tokens["border-thick"],
94
+ });
95
+ });
96
+
97
+ it("does not open when disabled", async () => {
98
+ const onOpenChange = jest.fn();
99
+ renderSelect({ onOpenChange, disabled: true });
100
+
101
+ await user.press(screen.getByTestId("ATL-Select-Trigger"));
102
+
103
+ expect(onOpenChange).not.toHaveBeenCalled();
104
+ });
105
+
106
+ it("applies disabled styling: subtle fill and disabled value text", () => {
107
+ renderSelect({ disabled: true });
108
+
109
+ expect(screen.getByTestId("ATL-Select-Trigger")).toHaveStyle({
110
+ backgroundColor: tokens["color-disabled--secondary"],
111
+ });
112
+ expect(screen.getByText("Select an option")).toHaveStyle({
113
+ color: tokens["color-disabled"],
114
+ });
115
+ });
116
+
117
+ it("applies critical border styling when invalid", () => {
118
+ renderSelect({ invalid: true });
119
+
120
+ expect(screen.getByTestId("ATL-Select-Trigger")).toHaveStyle({
121
+ borderColor: tokens["color-critical"],
122
+ });
123
+ });
124
+
125
+ it("exposes a combobox role and conveys disabled to the accessibility state", () => {
126
+ renderSelect({ disabled: true });
127
+
128
+ const trigger = screen.getByRole("combobox");
129
+ expect(trigger.props.accessibilityState).toMatchObject({ disabled: true });
130
+ });
131
+
132
+ it("announces the selected option as the trigger's accessibility value", () => {
133
+ renderSelect({ defaultValue: { value: "tor", label: "Toronto" } });
134
+
135
+ const trigger = screen.getByTestId("ATL-Select-Trigger");
136
+ expect(trigger.props.accessibilityValue).toEqual({ text: "Toronto" });
137
+ });
138
+ });
139
+
140
+ describe("SelectPrimitive.Trigger ref", () => {
141
+ it("forwards a ref exposing the trigger with open/close", () => {
142
+ const triggerRef = React.createRef<TriggerRef>();
143
+ renderSelect({ triggerRef });
144
+
145
+ expect(triggerRef.current).not.toBeNull();
146
+ expect(typeof triggerRef.current?.open).toBe("function");
147
+ expect(typeof triggerRef.current?.close).toBe("function");
148
+ });
149
+ });
150
+
151
+ const CITIES = [
152
+ { value: "tor", label: "Toronto" },
153
+ { value: "van", label: "Vancouver" },
154
+ { value: "edm", label: "Edmonton" },
155
+ ];
156
+
157
+ function renderSelectWithContent({
158
+ defaultValue,
159
+ onValueChange,
160
+ closeOnPress,
161
+ disabledItem,
162
+ }: {
163
+ defaultValue?: { value: string; label: string };
164
+ onValueChange?: (option?: { value: string; label: string }) => void;
165
+ closeOnPress?: boolean;
166
+ disabledItem?: string;
167
+ } = {}) {
168
+ return render(
169
+ <SelectPrimitive.Root
170
+ defaultValue={defaultValue}
171
+ onValueChange={onValueChange}
172
+ >
173
+ <SelectPrimitive.Trigger>
174
+ <SelectPrimitive.Value placeholder="Select an option" />
175
+ </SelectPrimitive.Trigger>
176
+ <SelectPrimitive.Portal>
177
+ <SelectPrimitive.Overlay />
178
+ <SelectPrimitive.Content>
179
+ <SelectPrimitive.Viewport>
180
+ <SelectPrimitive.Group>
181
+ <SelectPrimitive.Label>Cities</SelectPrimitive.Label>
182
+ {CITIES.map(city => (
183
+ <SelectPrimitive.Item
184
+ key={city.value}
185
+ value={city.value}
186
+ label={city.label}
187
+ closeOnPress={closeOnPress}
188
+ disabled={disabledItem === city.value}
189
+ >
190
+ <SelectPrimitive.ItemText
191
+ disabled={disabledItem === city.value}
192
+ />
193
+ <SelectPrimitive.ItemIndicator>
194
+ <Icon name="checkmark" />
195
+ </SelectPrimitive.ItemIndicator>
196
+ </SelectPrimitive.Item>
197
+ ))}
198
+ </SelectPrimitive.Group>
199
+ <SelectPrimitive.Separator />
200
+ </SelectPrimitive.Viewport>
201
+ </SelectPrimitive.Content>
202
+ </SelectPrimitive.Portal>
203
+ </SelectPrimitive.Root>,
204
+ { wrapper: PortalHostWrapper },
205
+ );
206
+ }
207
+
208
+ async function openSelect() {
209
+ await user.press(screen.getByTestId("ATL-Select-Trigger"));
210
+ }
211
+
212
+ describe("SelectPrimitive.Content", () => {
213
+ it("renders nothing until the trigger opens it", async () => {
214
+ renderSelectWithContent();
215
+ expect(screen.queryByText("Toronto")).toBeNull();
216
+
217
+ await openSelect();
218
+
219
+ expect(screen.getByText("Toronto")).toBeDefined();
220
+ expect(screen.getByText("Vancouver")).toBeDefined();
221
+ expect(screen.getByText("Cities")).toBeDefined();
222
+ });
223
+
224
+ it("dismisses when tapping outside (overlay), without selecting", async () => {
225
+ const onValueChange = jest.fn();
226
+ renderSelectWithContent({ onValueChange });
227
+ await openSelect();
228
+
229
+ await user.press(screen.getByTestId("ATL-Select-Overlay"));
230
+
231
+ expect(screen.queryByText("Toronto")).toBeNull();
232
+ expect(onValueChange).not.toHaveBeenCalled();
233
+ });
234
+ });
235
+
236
+ describe("SelectPrimitive.Item", () => {
237
+ it("selects the option, emits { value, label }, and closes", async () => {
238
+ const onValueChange = jest.fn();
239
+ renderSelectWithContent({ onValueChange });
240
+ await openSelect();
241
+
242
+ await user.press(screen.getByText("Vancouver"));
243
+
244
+ expect(onValueChange).toHaveBeenCalledWith({
245
+ value: "van",
246
+ label: "Vancouver",
247
+ });
248
+ // Dropdown closed; trigger now shows the selection.
249
+ expect(screen.queryByText("Cities")).toBeNull();
250
+ expect(screen.getByText("Vancouver")).toBeDefined();
251
+ });
252
+
253
+ it("passes closeOnPress through — dropdown stays open when false", async () => {
254
+ const onValueChange = jest.fn();
255
+ renderSelectWithContent({ onValueChange, closeOnPress: false });
256
+ await openSelect();
257
+
258
+ await user.press(screen.getByText("Vancouver"));
259
+
260
+ expect(onValueChange).toHaveBeenCalled();
261
+ expect(screen.getByText("Cities")).toBeDefined();
262
+ });
263
+
264
+ it("does not select a disabled option", async () => {
265
+ const onValueChange = jest.fn();
266
+ renderSelectWithContent({ onValueChange, disabledItem: "van" });
267
+ await openSelect();
268
+
269
+ await user.press(screen.getByText("Vancouver"));
270
+
271
+ expect(onValueChange).not.toHaveBeenCalled();
272
+ });
273
+
274
+ it("greys a disabled option's text when ItemText is told it is disabled", async () => {
275
+ renderSelectWithContent({ disabledItem: "van" });
276
+ await openSelect();
277
+
278
+ expect(screen.getByText("Vancouver")).toHaveStyle({
279
+ color: tokens["color-disabled"],
280
+ });
281
+ expect(screen.getByText("Toronto")).toHaveStyle({
282
+ color: tokens["color-text"],
283
+ });
284
+ });
285
+
286
+ it("shows the checkmark indicator only on the selected option", async () => {
287
+ renderSelectWithContent({
288
+ defaultValue: { value: "tor", label: "Toronto" },
289
+ });
290
+ await openSelect();
291
+
292
+ // One checkmark icon in the dropdown (the selected row's indicator).
293
+ expect(screen.getAllByTestId("checkmark")).toHaveLength(1);
294
+ });
295
+
296
+ it("renders whatever marker is composed into ItemIndicator on the selected row", async () => {
297
+ render(
298
+ <SelectPrimitive.Root defaultValue={{ value: "tor", label: "Toronto" }}>
299
+ <SelectPrimitive.Trigger>
300
+ <SelectPrimitive.Value placeholder="Select an option" />
301
+ </SelectPrimitive.Trigger>
302
+ <SelectPrimitive.Portal>
303
+ <SelectPrimitive.Overlay />
304
+ <SelectPrimitive.Content>
305
+ <SelectPrimitive.Viewport>
306
+ <SelectPrimitive.Item value="tor" label="Toronto">
307
+ <SelectPrimitive.ItemText />
308
+ <SelectPrimitive.ItemIndicator>
309
+ <Icon name="person" />
310
+ </SelectPrimitive.ItemIndicator>
311
+ </SelectPrimitive.Item>
312
+ </SelectPrimitive.Viewport>
313
+ </SelectPrimitive.Content>
314
+ </SelectPrimitive.Portal>
315
+ </SelectPrimitive.Root>,
316
+ { wrapper: PortalHostWrapper },
317
+ );
318
+ await openSelect();
319
+
320
+ expect(screen.getByTestId("person")).toBeDefined();
321
+ expect(screen.queryByTestId("checkmark")).toBeNull();
322
+ });
323
+
324
+ it("conveys option role plus selected and disabled states to assistive tech", async () => {
325
+ renderSelectWithContent({
326
+ defaultValue: { value: "tor", label: "Toronto" },
327
+ disabledItem: "van",
328
+ });
329
+ await openSelect();
330
+
331
+ const options = screen.getAllByRole("option");
332
+ const optionWith = (label: string) =>
333
+ options.find(option => within(option).queryByText(label));
334
+
335
+ expect(optionWith("Toronto")?.props.accessibilityState).toMatchObject({
336
+ checked: true,
337
+ });
338
+ expect(optionWith("Vancouver")?.props.accessibilityState).toMatchObject({
339
+ disabled: true,
340
+ });
341
+ });
342
+ });
343
+
344
+ describe("SelectPrimitive.Value", () => {
345
+ it("shows the placeholder with subdued styling when nothing is selected", () => {
346
+ renderSelect();
347
+
348
+ const placeholder = screen.getByText("Select an option");
349
+ expect(placeholder).toHaveStyle({
350
+ color: tokens["color-text--secondary"],
351
+ });
352
+ });
353
+
354
+ it("shows the selected option's label", () => {
355
+ renderSelect({ defaultValue: { value: "tor", label: "Toronto" } });
356
+
357
+ const value = screen.getByText("Toronto");
358
+ expect(value).toHaveStyle({ color: tokens["color-text"] });
359
+ expect(screen.queryByText("Select an option")).toBeNull();
360
+ });
361
+
362
+ it("greys the value when the field is disabled (set on Root)", () => {
363
+ renderSelect({
364
+ disabled: true,
365
+ defaultValue: { value: "tor", label: "Toronto" },
366
+ });
367
+
368
+ expect(screen.getByText("Toronto")).toHaveStyle({
369
+ color: tokens["color-disabled"],
370
+ });
371
+ });
372
+ });
@@ -0,0 +1,292 @@
1
+ import React from "react";
2
+ import { StyleSheet } from "react-native";
3
+ import * as Primitive from "@rn-primitives/select";
4
+ import { SafeAreaInsetsContext } from "react-native-safe-area-context";
5
+ import { useStyles } from "./SelectPrimitive.style";
6
+ import type {
7
+ SelectPrimitiveContentProps,
8
+ SelectPrimitiveGroupProps,
9
+ SelectPrimitiveItemIndicatorProps,
10
+ SelectPrimitiveItemProps,
11
+ SelectPrimitiveItemTextProps,
12
+ SelectPrimitiveLabelProps,
13
+ SelectPrimitiveOverlayProps,
14
+ SelectPrimitivePortalProps,
15
+ SelectPrimitiveRootProps,
16
+ SelectPrimitiveSeparatorProps,
17
+ SelectPrimitiveTriggerProps,
18
+ SelectPrimitiveValueProps,
19
+ SelectPrimitiveViewportProps,
20
+ } from "./types";
21
+ import { tokens as staticTokens } from "../../utils/design";
22
+
23
+ function SelectPrimitiveRoot({ ...props }: SelectPrimitiveRootProps) {
24
+ return <Primitive.Root {...props} />;
25
+ }
26
+
27
+ /**
28
+ * Styled trigger field. Renders only its `children` — compose
29
+ * `SelectPrimitive.Value` and any trailing icon (e.g. a chevron) here; the
30
+ * primitive provides no chevron of its own.
31
+ */
32
+ function SelectPrimitiveTrigger({
33
+ children,
34
+ style,
35
+ testID,
36
+ invalid,
37
+ onPressIn,
38
+ onPressOut,
39
+ ref,
40
+ ...props
41
+ }: SelectPrimitiveTriggerProps) {
42
+ const styles = useStyles();
43
+ const { open, value, disabled: rootDisabled } = Primitive.useRootContext();
44
+ const [pressed, setPressed] = React.useState(false);
45
+
46
+ // `disabled` is Root-level; the primitive's Trigger won't read it from
47
+ // context, so resolve it here and pass it down to block interaction.
48
+ const isDisabled = rootDisabled ?? false;
49
+ const isInvalid = !isDisabled && (invalid ?? false);
50
+
51
+ // Announce the selected option as the trigger's value.
52
+ const accessibilityValue = value?.label ? { text: value.label } : undefined;
53
+
54
+ return (
55
+ <Primitive.Trigger
56
+ ref={ref}
57
+ testID={computeTriggerTestID(testID)}
58
+ disabled={isDisabled}
59
+ accessibilityValue={accessibilityValue}
60
+ onPressIn={event => {
61
+ setPressed(true);
62
+ onPressIn?.(event);
63
+ }}
64
+ onPressOut={event => {
65
+ setPressed(false);
66
+ onPressOut?.(event);
67
+ }}
68
+ style={[
69
+ styles.trigger,
70
+ pressed && !open && styles.triggerPressed,
71
+ open && styles.triggerOpen,
72
+ isInvalid && styles.triggerInvalid,
73
+ isDisabled && styles.triggerDisabled,
74
+ style,
75
+ ]}
76
+ {...props}
77
+ >
78
+ {children}
79
+ </Primitive.Trigger>
80
+ );
81
+ }
82
+
83
+ function computeTriggerTestID(testID?: string): string {
84
+ return testID ? `ATL-${testID}-Select-Trigger` : "ATL-Select-Trigger";
85
+ }
86
+
87
+ function SelectPrimitiveValue({ style, ...props }: SelectPrimitiveValueProps) {
88
+ const styles = useStyles();
89
+ const { value, disabled } = Primitive.useRootContext();
90
+ const isEmpty = value?.value === undefined;
91
+
92
+ return (
93
+ <Primitive.Value
94
+ style={[
95
+ styles.value,
96
+ isEmpty && styles.valuePlaceholder,
97
+ disabled && styles.valueDisabled,
98
+ style,
99
+ ]}
100
+ {...props}
101
+ />
102
+ );
103
+ }
104
+
105
+ /** Portals the dropdown into the mounted `AtlantisPortalHost`. */
106
+ function SelectPrimitivePortal({ ...props }: SelectPrimitivePortalProps) {
107
+ return <Primitive.Portal {...props} />;
108
+ }
109
+
110
+ /** Tap-outside dismiss surface behind the dropdown (no scrim). */
111
+ function SelectPrimitiveOverlay({
112
+ style,
113
+ testID = "ATL-Select-Overlay",
114
+ ...props
115
+ }: SelectPrimitiveOverlayProps) {
116
+ const styles = useStyles();
117
+
118
+ return (
119
+ <Primitive.Overlay
120
+ testID={testID}
121
+ style={StyleSheet.flatten([styles.overlay, style])}
122
+ {...props}
123
+ />
124
+ );
125
+ }
126
+
127
+ /**
128
+ * Anchored dropdown surface: matches the trigger width, applies safe-area
129
+ * insets, and flips above the trigger when space is tight. Compose it inside
130
+ * `Portal`, alongside `Overlay`, wrapping the items in a `Viewport`.
131
+ */
132
+ function SelectPrimitiveContent({
133
+ children,
134
+ style,
135
+ sideOffset = staticTokens["space-smaller"],
136
+ insets,
137
+ ref,
138
+ ...props
139
+ }: SelectPrimitiveContentProps) {
140
+ const styles = useStyles();
141
+ // Read the context directly, not `useSafeAreaInsets` (which throws without a
142
+ // provider): `Content` renders at the portal host, which may sit outside one.
143
+ const safeAreaInsets = React.useContext(SafeAreaInsetsContext) ?? undefined;
144
+ const { triggerPosition } = Primitive.useRootContext();
145
+
146
+ return (
147
+ <Primitive.Content
148
+ ref={ref}
149
+ sideOffset={sideOffset}
150
+ insets={insets ?? safeAreaInsets}
151
+ style={StyleSheet.flatten([
152
+ styles.content,
153
+ triggerPosition ? { width: triggerPosition.width } : undefined,
154
+ style,
155
+ ])}
156
+ {...props}
157
+ >
158
+ {children}
159
+ </Primitive.Content>
160
+ );
161
+ }
162
+
163
+ /** Scroll container for the items (a no-op passthrough on native). */
164
+ function SelectPrimitiveViewport({ ...props }: SelectPrimitiveViewportProps) {
165
+ return <Primitive.Viewport {...props} />;
166
+ }
167
+
168
+ /**
169
+ * Styled, pressable option row. Compose its content from `SelectPrimitive.ItemText`
170
+ * and `SelectPrimitive.ItemIndicator` (and anything else) as `children`.
171
+ */
172
+ function SelectPrimitiveItem({
173
+ style,
174
+ disabled,
175
+ ref,
176
+ ...props
177
+ }: SelectPrimitiveItemProps) {
178
+ const styles = useStyles();
179
+
180
+ return (
181
+ <Primitive.Item
182
+ ref={ref}
183
+ disabled={disabled}
184
+ style={({ pressed }) => [
185
+ styles.item,
186
+ pressed && styles.itemPressed,
187
+ style,
188
+ ]}
189
+ {...props}
190
+ />
191
+ );
192
+ }
193
+
194
+ /**
195
+ * The option label as styled text (reads the label from the item context). The
196
+ * `disabled` flag is explicit — the composing layer (e.g. the sugared
197
+ * `Select.Item`) forwards the row's disabled state to grey the text.
198
+ */
199
+ function SelectPrimitiveItemText({
200
+ style,
201
+ disabled,
202
+ ref,
203
+ ...props
204
+ }: SelectPrimitiveItemTextProps) {
205
+ const styles = useStyles();
206
+
207
+ return (
208
+ <Primitive.ItemText
209
+ ref={ref}
210
+ style={[styles.itemText, disabled && styles.itemTextDisabled, style]}
211
+ {...props}
212
+ />
213
+ );
214
+ }
215
+
216
+ /**
217
+ * The selected-state marker slot. Renders its `children` (the marker, e.g. a
218
+ * checkmark) on the selected row only — it self-hides on unselected rows. The
219
+ * marker glyph is supplied by the composing layer, not defaulted here.
220
+ */
221
+ function SelectPrimitiveItemIndicator({
222
+ style,
223
+ ref,
224
+ ...props
225
+ }: SelectPrimitiveItemIndicatorProps) {
226
+ const styles = useStyles();
227
+
228
+ return (
229
+ <Primitive.ItemIndicator
230
+ ref={ref}
231
+ style={[styles.itemIndicator, style]}
232
+ {...props}
233
+ />
234
+ );
235
+ }
236
+
237
+ /**
238
+ * Groups related options, typically with a `SelectPrimitive.Label` heading.
239
+ */
240
+ function SelectPrimitiveGroup({ ...props }: SelectPrimitiveGroupProps) {
241
+ return <Primitive.Group {...props} />;
242
+ }
243
+
244
+ /**
245
+ * A styled group heading rendered inside `SelectPrimitive.Group`.
246
+ */
247
+ function SelectPrimitiveLabel({
248
+ style,
249
+ ref,
250
+ ...props
251
+ }: SelectPrimitiveLabelProps) {
252
+ const styles = useStyles();
253
+
254
+ return (
255
+ <Primitive.Label ref={ref} style={[styles.groupLabel, style]} {...props} />
256
+ );
257
+ }
258
+
259
+ /**
260
+ * A styled divider between groups or options.
261
+ */
262
+ function SelectPrimitiveSeparator({
263
+ style,
264
+ ref,
265
+ ...props
266
+ }: SelectPrimitiveSeparatorProps) {
267
+ const styles = useStyles();
268
+
269
+ return (
270
+ <Primitive.Separator
271
+ ref={ref}
272
+ style={[styles.separator, style]}
273
+ {...props}
274
+ />
275
+ );
276
+ }
277
+
278
+ export const SelectPrimitive = {
279
+ Root: SelectPrimitiveRoot,
280
+ Trigger: SelectPrimitiveTrigger,
281
+ Value: SelectPrimitiveValue,
282
+ Portal: SelectPrimitivePortal,
283
+ Overlay: SelectPrimitiveOverlay,
284
+ Content: SelectPrimitiveContent,
285
+ Viewport: SelectPrimitiveViewport,
286
+ Item: SelectPrimitiveItem,
287
+ ItemText: SelectPrimitiveItemText,
288
+ ItemIndicator: SelectPrimitiveItemIndicator,
289
+ Group: SelectPrimitiveGroup,
290
+ Label: SelectPrimitiveLabel,
291
+ Separator: SelectPrimitiveSeparator,
292
+ };
@@ -0,0 +1,17 @@
1
+ export { SelectPrimitive } from "./SelectPrimitive";
2
+ export type {
3
+ SelectPrimitiveContentProps,
4
+ SelectPrimitiveGroupProps,
5
+ SelectPrimitiveItemIndicatorProps,
6
+ SelectPrimitiveItemProps,
7
+ SelectPrimitiveItemTextProps,
8
+ SelectPrimitiveLabelProps,
9
+ SelectPrimitiveOption,
10
+ SelectPrimitiveOverlayProps,
11
+ SelectPrimitivePortalProps,
12
+ SelectPrimitiveRootProps,
13
+ SelectPrimitiveSeparatorProps,
14
+ SelectPrimitiveTriggerProps,
15
+ SelectPrimitiveValueProps,
16
+ SelectPrimitiveViewportProps,
17
+ } from "./types";