@jobber/components-native 0.108.1 → 0.109.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 (88) hide show
  1. package/dist/docs/AtlantisThemeContext/AtlantisThemeContext.md +40 -0
  2. package/dist/docs/Icon/Icon.md +60 -53
  3. package/dist/docs/Select/Select.md +987 -0
  4. package/dist/docs/index.md +1 -0
  5. package/dist/package.json +3 -3
  6. package/dist/src/Select/Select.composable.test.js +38 -0
  7. package/dist/src/Select/Select.js +26 -0
  8. package/dist/src/Select/SelectComposableTypes.js +1 -0
  9. package/dist/src/Select/SelectRoot.js +85 -0
  10. package/dist/src/Select/SelectRoot.style.js +9 -0
  11. package/dist/src/Select/SelectRoot.test.js +157 -0
  12. package/dist/src/Select/components/SelectContent.js +51 -0
  13. package/dist/src/Select/components/SelectContent.test.js +51 -0
  14. package/dist/src/Select/components/SelectFieldContext.js +10 -0
  15. package/dist/src/Select/components/SelectItem.js +29 -0
  16. package/dist/src/Select/components/SelectItem.style.js +6 -0
  17. package/dist/src/Select/components/SelectItem.test.js +98 -0
  18. package/dist/src/Select/components/SelectLabel.js +29 -0
  19. package/dist/src/Select/components/SelectTrigger.js +40 -0
  20. package/dist/src/Select/components/SelectTrigger.style.js +26 -0
  21. package/dist/src/Select/index.js +2 -0
  22. package/dist/src/primitives/Portal/index.js +3 -1
  23. package/dist/src/primitives/Select/SelectPrimitive.js +4 -12
  24. package/dist/src/primitives/Select/SelectPrimitive.style.js +14 -9
  25. package/dist/src/primitives/Select/SelectPrimitive.test.js +2 -2
  26. package/dist/src/primitives/Select/index.js +1 -1
  27. package/dist/src/utils/meta/meta.json +11 -0
  28. package/dist/tsconfig.build.tsbuildinfo +1 -1
  29. package/dist/types/src/Select/Select.composable.test.d.ts +1 -0
  30. package/dist/types/src/Select/Select.d.ts +18 -0
  31. package/dist/types/src/Select/SelectComposableTypes.d.ts +50 -0
  32. package/dist/types/src/Select/SelectRoot.d.ts +12 -0
  33. package/dist/types/src/Select/SelectRoot.style.d.ts +8 -0
  34. package/dist/types/src/Select/SelectRoot.test.d.ts +1 -0
  35. package/dist/types/src/Select/components/SelectContent.d.ts +8 -0
  36. package/dist/types/src/Select/components/SelectContent.test.d.ts +1 -0
  37. package/dist/types/src/Select/components/SelectFieldContext.d.ts +27 -0
  38. package/dist/types/src/Select/components/SelectItem.d.ts +32 -0
  39. package/dist/types/src/Select/components/SelectItem.style.d.ts +5 -0
  40. package/dist/types/src/Select/components/SelectItem.test.d.ts +1 -0
  41. package/dist/types/src/Select/components/SelectLabel.d.ts +22 -0
  42. package/dist/types/src/Select/components/SelectTrigger.d.ts +20 -0
  43. package/dist/types/src/Select/components/SelectTrigger.style.d.ts +21 -0
  44. package/dist/types/src/Select/index.d.ts +2 -1
  45. package/dist/types/src/primitives/Select/SelectPrimitive.d.ts +3 -1
  46. package/dist/types/src/primitives/Select/SelectPrimitive.style.d.ts +6 -3
  47. package/dist/types/src/primitives/Select/index.d.ts +1 -1
  48. package/dist/types/src/primitives/Select/types.d.ts +1 -1
  49. package/package.json +3 -3
  50. package/src/Select/Select.composable.stories.tsx +93 -0
  51. package/src/Select/Select.composable.test.tsx +56 -0
  52. package/src/Select/Select.guide.md +197 -0
  53. package/src/Select/Select.tsx +31 -0
  54. package/src/Select/SelectComposableTypes.ts +57 -0
  55. package/src/Select/SelectRoot.style.ts +10 -0
  56. package/src/Select/SelectRoot.test.tsx +245 -0
  57. package/src/Select/SelectRoot.tsx +120 -0
  58. package/src/Select/components/SelectContent.test.tsx +67 -0
  59. package/src/Select/components/SelectContent.tsx +75 -0
  60. package/src/Select/components/SelectFieldContext.tsx +46 -0
  61. package/src/Select/components/SelectItem.style.ts +7 -0
  62. package/src/Select/components/SelectItem.test.tsx +166 -0
  63. package/src/Select/components/SelectItem.tsx +78 -0
  64. package/src/Select/components/SelectLabel.tsx +57 -0
  65. package/src/Select/components/SelectTrigger.style.ts +29 -0
  66. package/src/Select/components/SelectTrigger.tsx +98 -0
  67. package/src/Select/docs/SelectComposableBasic.tsx +18 -0
  68. package/src/Select/docs/SelectComposableControlledWithRef.tsx +28 -0
  69. package/src/Select/docs/SelectComposableCustomMarker.tsx +25 -0
  70. package/src/Select/docs/SelectComposableDescription.tsx +18 -0
  71. package/src/Select/docs/SelectComposableDisabled.tsx +18 -0
  72. package/src/Select/docs/SelectComposableDisabledOptions.tsx +20 -0
  73. package/src/Select/docs/SelectComposableGroupedOptions.tsx +29 -0
  74. package/src/Select/docs/SelectComposableItemPrefixSuffix.tsx +34 -0
  75. package/src/Select/docs/SelectComposableLabelAbove.tsx +18 -0
  76. package/src/Select/docs/SelectComposableLabelInside.tsx +18 -0
  77. package/src/Select/docs/SelectComposableProvinces.tsx +36 -0
  78. package/src/Select/docs/SelectComposableReadOnly.tsx +18 -0
  79. package/src/Select/docs/SelectComposableValidationFlow.tsx +27 -0
  80. package/src/Select/docs/index.ts +14 -0
  81. package/src/Select/index.ts +10 -1
  82. package/src/primitives/Portal/index.ts +3 -1
  83. package/src/primitives/Select/SelectPrimitive.style.ts +18 -11
  84. package/src/primitives/Select/SelectPrimitive.test.tsx +2 -2
  85. package/src/primitives/Select/SelectPrimitive.tsx +6 -18
  86. package/src/primitives/Select/index.ts +4 -1
  87. package/src/primitives/Select/types.ts +1 -1
  88. package/src/utils/meta/meta.json +11 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,11 @@
1
1
  import type { ReactElement } from "react";
2
2
  import React from "react";
3
3
  import type { RegisterOptions } from "react-hook-form";
4
+ import { SelectRoot } from "./SelectRoot";
5
+ import { SelectTrigger } from "./components/SelectTrigger";
6
+ import { SelectLabel } from "./components/SelectLabel";
7
+ import { SelectContent } from "./components/SelectContent";
8
+ import { SelectItem, SelectItemPrefix, SelectItemSuffix } from "./components/SelectItem";
4
9
  export interface SelectOption {
5
10
  /**
6
11
  * Text that shows up as the option
@@ -71,4 +76,17 @@ export interface SelectProps {
71
76
  readonly testID?: string;
72
77
  }
73
78
  export declare function Select({ value, defaultValue, onChange, children, placeholder, label, assistiveText, disabled, invalid, validations, accessibilityLabel, name, testID, }: SelectProps): React.JSX.Element;
79
+ export declare namespace Select {
80
+ var Root: typeof SelectRoot;
81
+ var Trigger: typeof SelectTrigger;
82
+ var Value: ({ style, ...props }: import("../primitives").SelectPrimitiveValueProps) => React.JSX.Element;
83
+ var Label: typeof SelectLabel;
84
+ var Content: typeof SelectContent;
85
+ var Item: typeof SelectItem;
86
+ var ItemPrefix: typeof SelectItemPrefix;
87
+ var ItemSuffix: typeof SelectItemSuffix;
88
+ var Group: ({ ...props }: import("../primitives").SelectPrimitiveGroupProps) => React.JSX.Element;
89
+ var GroupLabel: ({ style, ref, ...props }: import("../primitives").SelectPrimitiveLabelProps) => React.JSX.Element;
90
+ var Separator: ({ style, ref, ...props }: import("../primitives").SelectPrimitiveSeparatorProps) => React.JSX.Element;
91
+ }
74
92
  export declare function Option({ children }: SelectOption): React.JSX.Element;
@@ -0,0 +1,50 @@
1
+ import type { ReactNode } from "react";
2
+ import type { SelectPrimitiveRootProps } from "../primitives/Select";
3
+ /**
4
+ * Props for the composable `Select.Root` chrome — a presentational
5
+ * composition over `SelectPrimitive.Root`. Adds the optional field chrome
6
+ * (label, description / error) and the field-state flags (`invalid`,
7
+ * `status`, `readOnly`) that the styled trigger reads. Everything else passes
8
+ * through to `SelectPrimitive.Root` (and therefore `@rn-primitives/select`).
9
+ *
10
+ * Deliberately RHF-free: no `name`, no `validations`.
11
+ */
12
+ export interface SelectRootProps extends SelectPrimitiveRootProps {
13
+ /**
14
+ * Where the field label renders: `"inside"` the trigger (small, above the value)
15
+ * or `"above"` it. DS-owned default (`"inside"`); overridable per instance.
16
+ * The label content itself is authored via `Select.Label`.
17
+ */
18
+ readonly labelPlacement?: "above" | "inside";
19
+ /**
20
+ * Explicit accessible name for the trigger. Auto-derived from a string
21
+ * `Select.Label`; supply this when the label content is a non-string
22
+ * `ReactNode`, or to name a field that has no visible `Select.Label`.
23
+ */
24
+ readonly accessibilityLabel?: string;
25
+ /**
26
+ * Supporting text rendered below the trigger. A string renders as neutral
27
+ * `HelperText`; a `ReactNode` renders verbatim. Suppressed when `error` is
28
+ * present.
29
+ */
30
+ readonly description?: string | ReactNode;
31
+ /**
32
+ * Error message rendered below the trigger as critical `HelperText` (string)
33
+ * or verbatim (`ReactNode`). Takes priority over `description` and applies
34
+ * the critical trigger styling.
35
+ */
36
+ readonly error?: string | ReactNode;
37
+ /**
38
+ * Applies the critical trigger styling without rendering an error message.
39
+ */
40
+ readonly invalid?: boolean;
41
+ /**
42
+ * `critical` applies the critical trigger styling. Defaults to `neutral`.
43
+ */
44
+ readonly status?: "neutral" | "critical";
45
+ /**
46
+ * Renders the trigger as non-interactive read-only presentation (subtle
47
+ * surface, no chevron, value as text). `disabled` takes precedence.
48
+ */
49
+ readonly readOnly?: boolean;
50
+ }
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import type { SelectRootProps } from "./SelectComposableTypes";
3
+ /**
4
+ * Composable `Select.Root` chrome: renders the optional `description` / `error`
5
+ * below the field (via `HelperText`), owns the field-state context, and places the
6
+ * field label. The label content is authored via `Select.Label` (registered into
7
+ * this context); `labelPlacement` decides whether it renders in the above slot
8
+ * (here) or the inside slot (`Select.Trigger`). `error` takes priority over
9
+ * `description`. See `Select.guide.md` for the label / accessibility / layering
10
+ * model.
11
+ */
12
+ export declare function SelectRoot({ labelPlacement, accessibilityLabel: accessibilityLabelProp, description, error, invalid, status, readOnly, children, ...rootProps }: SelectRootProps): React.JSX.Element;
@@ -0,0 +1,8 @@
1
+ export declare const useStyles: () => {
2
+ container: {
3
+ gap: number;
4
+ };
5
+ field: {
6
+ gap: number;
7
+ };
8
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { ReactElement } from "react";
2
+ import type { SelectPrimitiveContentProps } from "../../primitives/Select";
3
+ export type SelectContentProps = SelectPrimitiveContentProps;
4
+ /**
5
+ * Folds the primitive Portal / Overlay / Content / Viewport into one dropdown
6
+ * part. Positioning lives in the primitive `Content`; props pass through.
7
+ */
8
+ export declare function SelectContent({ children, ...props }: SelectContentProps): ReactElement;
@@ -0,0 +1,27 @@
1
+ import type { ReactNode } from "react";
2
+ /** Where the field label renders relative to the trigger. */
3
+ export type LabelPlacement = "above" | "inside";
4
+ /**
5
+ * Sugar-owned field state, provided by `Select.Root` and read by the sugar parts
6
+ * (`Select.Trigger`, `Select.Label`). `Select.Label` registers its content here
7
+ * (rather than rendering in place); `Select.Root` / `Select.Trigger` render it in
8
+ * the slot `labelPlacement` selects. See `Select.guide.md` for the model.
9
+ */
10
+ export interface SelectFieldState {
11
+ /** Chosen label placement — above the trigger or inside it. */
12
+ readonly labelPlacement: LabelPlacement;
13
+ /** The label content to render — the registered `Select.Label` children. */
14
+ readonly labelContent?: ReactNode;
15
+ /** Resolved accessible name applied to the trigger. */
16
+ readonly accessibilityLabel?: string;
17
+ /** Resolved critical state (from `invalid` / `status` / `error`). */
18
+ readonly invalid?: boolean;
19
+ /** Render the trigger as a static, non-pressable read-only display. */
20
+ readonly readOnly?: boolean;
21
+ /** Register the label content (+ its accessible name, when it is a string). */
22
+ registerLabel(content: ReactNode, accessibleName?: string): void;
23
+ /** Clear a previously registered label (on `Select.Label` unmount). */
24
+ unregisterLabel(): void;
25
+ }
26
+ export declare const SelectFieldContext: import("react").Context<SelectFieldState>;
27
+ export declare function useSelectFieldState(): SelectFieldState;
@@ -0,0 +1,32 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ export interface SelectItemProps {
3
+ /** The option's value. */
4
+ readonly value: string;
5
+ /** The option's label — rendered as the row text and used as the value label. */
6
+ readonly label: string;
7
+ /** Disables selection and greys the label. */
8
+ readonly disabled?: boolean;
9
+ /** Close the dropdown when this option is pressed (default behaviour). */
10
+ readonly closeOnPress?: boolean;
11
+ /** Leading content, before the label — typically a `Select.ItemPrefix`. */
12
+ readonly prefix?: ReactNode;
13
+ /** Trailing content, after the label — typically a `Select.ItemSuffix`. */
14
+ readonly suffix?: ReactNode;
15
+ /** Overrides the default checkmark marker shown on the selected row. */
16
+ readonly indicator?: ReactNode;
17
+ }
18
+ /**
19
+ * An option row. Auto-composes the label + a default checkmark from its
20
+ * `value` / `label` / `disabled` props, and places the `prefix` / `suffix` slot
21
+ * content around the label (prefix → label → suffix → marker). `indicator`
22
+ * overrides the default checkmark; `disabled` greys the label and blocks
23
+ * selection.
24
+ */
25
+ export declare function SelectItem({ value, label, disabled, closeOnPress, prefix, suffix, indicator, }: SelectItemProps): ReactElement;
26
+ export interface SelectItemSlotProps {
27
+ readonly children?: ReactNode;
28
+ }
29
+ /** Styled leading slot — pass to `Select.Item`'s `prefix` prop. */
30
+ export declare function SelectItemPrefix({ children, }: SelectItemSlotProps): ReactElement;
31
+ /** Styled trailing slot — pass to `Select.Item`'s `suffix` prop. */
32
+ export declare function SelectItemSuffix({ children, }: SelectItemSlotProps): ReactElement;
@@ -0,0 +1,5 @@
1
+ export declare const useStyles: () => {
2
+ slot: {
3
+ justifyContent: "center";
4
+ };
5
+ };
@@ -0,0 +1,22 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ export interface SelectLabelProps {
3
+ /**
4
+ * The label content. Its placement (above the trigger vs. inside it) is set by
5
+ * the `labelPlacement` prop on `Select.Root`, not by where this is written.
6
+ */
7
+ readonly children?: ReactNode;
8
+ }
9
+ /**
10
+ * The field label. Rather than render in place, it **registers** its `children`
11
+ * (and, when a string, the trigger's accessible name) into `Select.Root`, which
12
+ * renders the content in the slot `labelPlacement` selects. Renders nothing where
13
+ * it is composed. See `Select.guide.md`.
14
+ */
15
+ export declare function SelectLabel({ children }: SelectLabelProps): null;
16
+ /**
17
+ * Internal renderer for the registered label content, shared by the above slot
18
+ * (`Select.Root`) and the inside slot (`Select.Trigger`). Sizes itself from
19
+ * `labelPlacement` (smaller when `inside`). Hidden from assistive tech — the
20
+ * trigger carries the accessible name.
21
+ */
22
+ export declare function FieldLabel(): ReactElement | null;
@@ -0,0 +1,20 @@
1
+ import type { ReactElement, ReactNode, Ref } from "react";
2
+ import { type TriggerRef } from "@rn-primitives/select";
3
+ export interface SelectTriggerProps {
4
+ /** Trigger content — typically `Select.Value` (and optionally a label part). */
5
+ readonly children?: ReactNode;
6
+ /** Used verbatim to locate the trigger in end-to-end tests. Defaults to `ATL-Select-Trigger`. */
7
+ readonly testID?: string;
8
+ /**
9
+ * Imperative handle exposing `open()` / `close()`. Stays `null` when the field
10
+ * is `readOnly` (no interactive trigger is mounted).
11
+ */
12
+ readonly ref?: Ref<TriggerRef>;
13
+ }
14
+ /**
15
+ * The styled field trigger: composes the chevron, the resolved `invalid` styling,
16
+ * the accessible name, the inside label placement, and the static `readOnly`
17
+ * presentation over `SelectPrimitive.Trigger`. State comes from `Select.Root`
18
+ * (field context) and the primitive root context.
19
+ */
20
+ export declare function SelectTrigger({ children, testID, ref, }: SelectTriggerProps): ReactElement;
@@ -0,0 +1,21 @@
1
+ export declare const useStyles: () => {
2
+ content: {
3
+ flex: number;
4
+ justifyContent: "center";
5
+ };
6
+ contentInside: {
7
+ paddingTop: number;
8
+ };
9
+ readOnly: {
10
+ flexDirection: "row";
11
+ alignItems: "center";
12
+ gap: number;
13
+ minHeight: number;
14
+ paddingLeft: number;
15
+ paddingRight: number;
16
+ backgroundColor: string;
17
+ borderWidth: number;
18
+ borderColor: string;
19
+ borderRadius: number;
20
+ };
21
+ };
@@ -1,3 +1,4 @@
1
- export type { SelectOption, SelectProps } from "./Select";
2
1
  export { Select, Option } from "./Select";
3
2
  export { SelectPressable } from "./components/SelectPressable";
3
+ export type { SelectOption, SelectProps } from "./Select";
4
+ export type { SelectRootProps } from "./SelectComposableTypes";
@@ -1,12 +1,14 @@
1
1
  import React from "react";
2
2
  import type { SelectPrimitiveContentProps, SelectPrimitiveGroupProps, SelectPrimitiveItemIndicatorProps, SelectPrimitiveItemProps, SelectPrimitiveItemTextProps, SelectPrimitiveLabelProps, SelectPrimitiveOverlayProps, SelectPrimitivePortalProps, SelectPrimitiveRootProps, SelectPrimitiveSeparatorProps, SelectPrimitiveTriggerProps, SelectPrimitiveValueProps, SelectPrimitiveViewportProps } from "./types";
3
+ /** Default trigger testID. Consumer-supplied `testID`s are used verbatim. */
4
+ export declare const SELECT_TRIGGER_DEFAULT_TEST_ID = "ATL-Select-Trigger";
3
5
  declare function SelectPrimitiveRoot({ ...props }: SelectPrimitiveRootProps): React.JSX.Element;
4
6
  /**
5
7
  * Styled trigger field. Renders only its `children` — compose
6
8
  * `SelectPrimitive.Value` and any trailing icon (e.g. a chevron) here; the
7
9
  * primitive provides no chevron of its own.
8
10
  */
9
- declare function SelectPrimitiveTrigger({ children, style, testID, invalid, onPressIn, onPressOut, ref, ...props }: SelectPrimitiveTriggerProps): React.JSX.Element;
11
+ declare function SelectPrimitiveTrigger({ children, style, testID, invalid, ref, ...props }: SelectPrimitiveTriggerProps): React.JSX.Element;
10
12
  declare function SelectPrimitiveValue({ style, ...props }: SelectPrimitiveValueProps): React.JSX.Element;
11
13
  /** Portals the dropdown into the mounted `AtlantisPortalHost`. */
12
14
  declare function SelectPrimitivePortal({ ...props }: SelectPrimitivePortalProps): React.JSX.Element;
@@ -4,7 +4,8 @@ export declare const useStyles: () => {
4
4
  alignItems: "center";
5
5
  gap: number;
6
6
  minHeight: number;
7
- paddingHorizontal: number;
7
+ paddingLeft: number;
8
+ paddingRight: number;
8
9
  backgroundColor: string;
9
10
  borderWidth: number;
10
11
  borderColor: string;
@@ -13,12 +14,14 @@ export declare const useStyles: () => {
13
14
  triggerOpen: {
14
15
  borderWidth: number;
15
16
  borderColor: string;
16
- paddingHorizontal: number;
17
+ paddingLeft: number;
18
+ paddingRight: number;
17
19
  };
18
20
  triggerPressed: {
19
21
  borderWidth: number;
20
22
  borderColor: string;
21
- paddingHorizontal: number;
23
+ paddingLeft: number;
24
+ paddingRight: number;
22
25
  };
23
26
  triggerInvalid: {
24
27
  borderColor: string;
@@ -1,2 +1,2 @@
1
- export { SelectPrimitive } from "./SelectPrimitive";
1
+ export { SelectPrimitive, SELECT_TRIGGER_DEFAULT_TEST_ID, } from "./SelectPrimitive";
2
2
  export type { SelectPrimitiveContentProps, SelectPrimitiveGroupProps, SelectPrimitiveItemIndicatorProps, SelectPrimitiveItemProps, SelectPrimitiveItemTextProps, SelectPrimitiveLabelProps, SelectPrimitiveOption, SelectPrimitiveOverlayProps, SelectPrimitivePortalProps, SelectPrimitiveRootProps, SelectPrimitiveSeparatorProps, SelectPrimitiveTriggerProps, SelectPrimitiveValueProps, SelectPrimitiveViewportProps, } from "./types";
@@ -4,7 +4,7 @@ import type { ContentProps, ContentRef, GroupProps, GroupRef, ItemIndicatorProps
4
4
  export type { Option as SelectPrimitiveOption };
5
5
  export type SelectPrimitiveRootProps = RootProps;
6
6
  export interface SelectPrimitiveTriggerProps extends Omit<TriggerProps, "style" | "children" | "disabled" | "asChild">, React.RefAttributes<TriggerRef> {
7
- /** Renders as `ATL-<testID>-Select-Trigger`, or `ATL-Select-Trigger` when omitted. */
7
+ /** Used verbatim to locate the trigger in end-to-end tests. Defaults to `ATL-Select-Trigger`. */
8
8
  readonly testID?: string;
9
9
  /** Critical (error) border styling. Style-only — no field/validation logic. */
10
10
  readonly invalid?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.108.1",
3
+ "version": "0.109.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.107.0",
76
+ "@jobber/design": "0.108.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": "7d8b08e91839f535411288b03a8603f2df310a8e"
127
+ "gitHead": "9c3f49230e6823f32b857348eea4efa3fb32aa2a"
128
128
  }
@@ -0,0 +1,93 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
2
+ import React from "react";
3
+ import { View } from "react-native";
4
+ import { SafeAreaProvider } from "react-native-safe-area-context";
5
+ import { PortalHost } from "@rn-primitives/portal";
6
+ import { Select } from "@jobber/components-native";
7
+ import {
8
+ SelectComposableBasic,
9
+ SelectComposableControlledWithRef,
10
+ SelectComposableCustomMarker,
11
+ SelectComposableDescription,
12
+ SelectComposableDisabled,
13
+ SelectComposableDisabledOptions,
14
+ SelectComposableGroupedOptions,
15
+ SelectComposableItemPrefixSuffix,
16
+ SelectComposableLabelAbove,
17
+ SelectComposableLabelInside,
18
+ SelectComposableReadOnly,
19
+ SelectComposableValidationFlow,
20
+ } from "./docs";
21
+
22
+ const meta = {
23
+ title: "Components/Selections/Select/SelectComposable",
24
+ component: Select.Root,
25
+ parameters: {
26
+ viewport: { defaultViewport: "mobile1" },
27
+ },
28
+ decorators: [
29
+ // PortalHost outside SafeAreaProvider so the portal-based dropdown renders
30
+ // and resolves its position against the page origin (see the SelectPrimitive
31
+ // stories for the rationale). Apps mount one PortalHost at their root.
32
+ (Story: React.ComponentType) => (
33
+ <>
34
+ <SafeAreaProvider>
35
+ <View style={{ width: 320, padding: 16, minHeight: 560 }}>
36
+ <Story />
37
+ </View>
38
+ </SafeAreaProvider>
39
+ <PortalHost />
40
+ </>
41
+ ),
42
+ ],
43
+ } satisfies Meta<typeof Select.Root>;
44
+ export default meta;
45
+ type Story = StoryObj<typeof meta>;
46
+
47
+ // ── Field & label placement ────────────────────────────────────────────────
48
+
49
+ /** Default placement: the composed `Select.Label` renders inside the trigger. */
50
+ export const Basic: Story = { render: SelectComposableBasic };
51
+
52
+ /** `labelPlacement="above"` renders the same `Select.Label` above the trigger. */
53
+ export const LabelAbove: Story = { render: SelectComposableLabelAbove };
54
+
55
+ /** `labelPlacement="inside"` renders the label as a small label inside the trigger. */
56
+ export const LabelInside: Story = { render: SelectComposableLabelInside };
57
+
58
+ /** Supporting `description` rendered below the field. */
59
+ export const Description: Story = { render: SelectComposableDescription };
60
+
61
+ /** Validation: the `error` message and critical styling clear once selected. */
62
+ export const ValidationFlow: Story = { render: SelectComposableValidationFlow };
63
+
64
+ // ── Options & content ──────────────────────────────────────────────────────
65
+
66
+ /** Grouped options with group labels and a separator between groups. */
67
+ export const GroupedOptions: Story = { render: SelectComposableGroupedOptions };
68
+
69
+ /** `Select.ItemPrefix` / `Select.ItemSuffix` slots passed into `Select.Item`. */
70
+ export const ItemPrefixSuffix: Story = {
71
+ render: SelectComposableItemPrefixSuffix,
72
+ };
73
+
74
+ /** Override the default checkmark via the `indicator` slot prop. */
75
+ export const CustomMarker: Story = { render: SelectComposableCustomMarker };
76
+
77
+ /** Individual options can be disabled. */
78
+ export const DisabledOptions: Story = {
79
+ render: SelectComposableDisabledOptions,
80
+ };
81
+
82
+ // ── Behaviour & field states ───────────────────────────────────────────────
83
+
84
+ /** The trigger ref exposes imperative `open()` / `close()`. */
85
+ export const ControlledWithRef: Story = {
86
+ render: SelectComposableControlledWithRef,
87
+ };
88
+
89
+ /** Read-only: non-interactive, value rendered as text with no chevron. */
90
+ export const ReadOnly: Story = { render: SelectComposableReadOnly };
91
+
92
+ /** Disabled field. */
93
+ export const Disabled: Story = { render: SelectComposableDisabled };
@@ -0,0 +1,56 @@
1
+ import React from "react";
2
+ import { render, screen } from "@testing-library/react-native";
3
+ import { Option, Select } from ".";
4
+ import { PortalHostWrapper } from "../utils/test";
5
+
6
+ describe("Select composable statics + coexistence", () => {
7
+ it("exposes the composable parts as statics on Select", () => {
8
+ expect(Object.keys(Select)).toEqual(
9
+ expect.arrayContaining([
10
+ "Root",
11
+ "Trigger",
12
+ "Value",
13
+ "Label",
14
+ "Content",
15
+ "Item",
16
+ "Group",
17
+ "GroupLabel",
18
+ "Separator",
19
+ ]),
20
+ );
21
+ });
22
+
23
+ it("renders through the public statics with no react-hook-form context", () => {
24
+ render(
25
+ <Select.Root>
26
+ <Select.Label>City</Select.Label>
27
+ <Select.Trigger>
28
+ <Select.Value placeholder="Select a city" />
29
+ </Select.Trigger>
30
+ <Select.Content>
31
+ <Select.Item value="tor" label="Toronto" />
32
+ </Select.Content>
33
+ </Select.Root>,
34
+ { wrapper: PortalHostWrapper },
35
+ );
36
+
37
+ expect(
38
+ screen.getByText("City", { includeHiddenElements: true }),
39
+ ).toBeDefined();
40
+ expect(screen.getByTestId("ATL-Select-Trigger")).toBeDefined();
41
+ });
42
+
43
+ it("keeps the legacy props-driven Select callable and renders Option", () => {
44
+ render(
45
+ <Select onChange={jest.fn()}>
46
+ <Option value="tor">Toronto</Option>
47
+ <Option value="van">Vancouver</Option>
48
+ </Select>,
49
+ );
50
+
51
+ // Legacy trigger testID is preserved and does not collide with the
52
+ // composable trigger's ATL-Select-Trigger.
53
+ expect(screen.getByTestId("ATL-Select")).toBeDefined();
54
+ expect(screen.queryByTestId("ATL-Select-Trigger")).toBeNull();
55
+ });
56
+ });