@jobber/components-native 0.105.4-addeditor-cab44cf.11 → 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 +2 -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 @@
1
+ export { PortalHost as AtlantisPortalHost } from "@rn-primitives/portal";
@@ -0,0 +1,67 @@
1
+ import React from "react";
2
+ import type { SelectPrimitiveContentProps, SelectPrimitiveGroupProps, SelectPrimitiveItemIndicatorProps, SelectPrimitiveItemProps, SelectPrimitiveItemTextProps, SelectPrimitiveLabelProps, SelectPrimitiveOverlayProps, SelectPrimitivePortalProps, SelectPrimitiveRootProps, SelectPrimitiveSeparatorProps, SelectPrimitiveTriggerProps, SelectPrimitiveValueProps, SelectPrimitiveViewportProps } from "./types";
3
+ declare function SelectPrimitiveRoot({ ...props }: SelectPrimitiveRootProps): React.JSX.Element;
4
+ /**
5
+ * Styled trigger field. Renders only its `children` — compose
6
+ * `SelectPrimitive.Value` and any trailing icon (e.g. a chevron) here; the
7
+ * primitive provides no chevron of its own.
8
+ */
9
+ declare function SelectPrimitiveTrigger({ children, style, testID, invalid, onPressIn, onPressOut, ref, ...props }: SelectPrimitiveTriggerProps): React.JSX.Element;
10
+ declare function SelectPrimitiveValue({ style, ...props }: SelectPrimitiveValueProps): React.JSX.Element;
11
+ /** Portals the dropdown into the mounted `AtlantisPortalHost`. */
12
+ declare function SelectPrimitivePortal({ ...props }: SelectPrimitivePortalProps): React.JSX.Element;
13
+ /** Tap-outside dismiss surface behind the dropdown (no scrim). */
14
+ declare function SelectPrimitiveOverlay({ style, testID, ...props }: SelectPrimitiveOverlayProps): React.JSX.Element;
15
+ /**
16
+ * Anchored dropdown surface: matches the trigger width, applies safe-area
17
+ * insets, and flips above the trigger when space is tight. Compose it inside
18
+ * `Portal`, alongside `Overlay`, wrapping the items in a `Viewport`.
19
+ */
20
+ declare function SelectPrimitiveContent({ children, style, sideOffset, insets, ref, ...props }: SelectPrimitiveContentProps): React.JSX.Element;
21
+ /** Scroll container for the items (a no-op passthrough on native). */
22
+ declare function SelectPrimitiveViewport({ ...props }: SelectPrimitiveViewportProps): React.JSX.Element;
23
+ /**
24
+ * Styled, pressable option row. Compose its content from `SelectPrimitive.ItemText`
25
+ * and `SelectPrimitive.ItemIndicator` (and anything else) as `children`.
26
+ */
27
+ declare function SelectPrimitiveItem({ style, disabled, ref, ...props }: SelectPrimitiveItemProps): React.JSX.Element;
28
+ /**
29
+ * The option label as styled text (reads the label from the item context). The
30
+ * `disabled` flag is explicit — the composing layer (e.g. the sugared
31
+ * `Select.Item`) forwards the row's disabled state to grey the text.
32
+ */
33
+ declare function SelectPrimitiveItemText({ style, disabled, ref, ...props }: SelectPrimitiveItemTextProps): React.JSX.Element;
34
+ /**
35
+ * The selected-state marker slot. Renders its `children` (the marker, e.g. a
36
+ * checkmark) on the selected row only — it self-hides on unselected rows. The
37
+ * marker glyph is supplied by the composing layer, not defaulted here.
38
+ */
39
+ declare function SelectPrimitiveItemIndicator({ style, ref, ...props }: SelectPrimitiveItemIndicatorProps): React.JSX.Element;
40
+ /**
41
+ * Groups related options, typically with a `SelectPrimitive.Label` heading.
42
+ */
43
+ declare function SelectPrimitiveGroup({ ...props }: SelectPrimitiveGroupProps): React.JSX.Element;
44
+ /**
45
+ * A styled group heading rendered inside `SelectPrimitive.Group`.
46
+ */
47
+ declare function SelectPrimitiveLabel({ style, ref, ...props }: SelectPrimitiveLabelProps): React.JSX.Element;
48
+ /**
49
+ * A styled divider between groups or options.
50
+ */
51
+ declare function SelectPrimitiveSeparator({ style, ref, ...props }: SelectPrimitiveSeparatorProps): React.JSX.Element;
52
+ export declare const SelectPrimitive: {
53
+ Root: typeof SelectPrimitiveRoot;
54
+ Trigger: typeof SelectPrimitiveTrigger;
55
+ Value: typeof SelectPrimitiveValue;
56
+ Portal: typeof SelectPrimitivePortal;
57
+ Overlay: typeof SelectPrimitiveOverlay;
58
+ Content: typeof SelectPrimitiveContent;
59
+ Viewport: typeof SelectPrimitiveViewport;
60
+ Item: typeof SelectPrimitiveItem;
61
+ ItemText: typeof SelectPrimitiveItemText;
62
+ ItemIndicator: typeof SelectPrimitiveItemIndicator;
63
+ Group: typeof SelectPrimitiveGroup;
64
+ Label: typeof SelectPrimitiveLabel;
65
+ Separator: typeof SelectPrimitiveSeparator;
66
+ };
67
+ export {};
@@ -0,0 +1,105 @@
1
+ export declare const useStyles: () => {
2
+ trigger: {
3
+ flexDirection: "row";
4
+ alignItems: "center";
5
+ gap: number;
6
+ minHeight: number;
7
+ paddingHorizontal: number;
8
+ backgroundColor: string;
9
+ borderWidth: number;
10
+ borderColor: string;
11
+ borderRadius: number;
12
+ };
13
+ triggerOpen: {
14
+ borderWidth: number;
15
+ borderColor: string;
16
+ paddingHorizontal: number;
17
+ };
18
+ triggerPressed: {
19
+ borderWidth: number;
20
+ borderColor: string;
21
+ paddingHorizontal: number;
22
+ };
23
+ triggerInvalid: {
24
+ borderColor: string;
25
+ };
26
+ triggerDisabled: {
27
+ backgroundColor: string;
28
+ };
29
+ value: {
30
+ flex: number;
31
+ color: string;
32
+ fontFamily: string | undefined;
33
+ fontSize: number | undefined;
34
+ lineHeight: number;
35
+ letterSpacing: number | undefined;
36
+ };
37
+ valuePlaceholder: {
38
+ color: string;
39
+ };
40
+ valueDisabled: {
41
+ color: string;
42
+ };
43
+ overlay: {
44
+ position: "absolute";
45
+ top: number;
46
+ right: number;
47
+ bottom: number;
48
+ left: number;
49
+ };
50
+ content: {
51
+ shadowColor: string;
52
+ shadowOffset: {
53
+ width: number;
54
+ height: number;
55
+ };
56
+ shadowOpacity: number;
57
+ shadowRadius: number;
58
+ elevation: number;
59
+ backgroundColor: string;
60
+ borderWidth: number;
61
+ borderColor: string;
62
+ borderRadius: number;
63
+ padding: number;
64
+ };
65
+ item: {
66
+ flexDirection: "row";
67
+ alignItems: "center";
68
+ gap: number;
69
+ minHeight: number;
70
+ padding: number;
71
+ borderRadius: number;
72
+ };
73
+ itemPressed: {
74
+ backgroundColor: string;
75
+ };
76
+ itemText: {
77
+ flex: number;
78
+ color: string;
79
+ fontFamily: string | undefined;
80
+ fontSize: number | undefined;
81
+ lineHeight: number;
82
+ letterSpacing: number | undefined;
83
+ };
84
+ itemTextDisabled: {
85
+ color: string;
86
+ };
87
+ itemIndicator: {
88
+ justifyContent: "center";
89
+ alignItems: "center";
90
+ };
91
+ groupLabel: {
92
+ color: string;
93
+ fontFamily: string | undefined;
94
+ fontSize: number;
95
+ lineHeight: number;
96
+ paddingTop: number;
97
+ paddingBottom: number;
98
+ paddingHorizontal: number;
99
+ };
100
+ separator: {
101
+ height: number;
102
+ backgroundColor: string;
103
+ marginVertical: number;
104
+ };
105
+ };
@@ -0,0 +1,2 @@
1
+ export { SelectPrimitive } from "./SelectPrimitive";
2
+ export type { SelectPrimitiveContentProps, SelectPrimitiveGroupProps, SelectPrimitiveItemIndicatorProps, SelectPrimitiveItemProps, SelectPrimitiveItemTextProps, SelectPrimitiveLabelProps, SelectPrimitiveOption, SelectPrimitiveOverlayProps, SelectPrimitivePortalProps, SelectPrimitiveRootProps, SelectPrimitiveSeparatorProps, SelectPrimitiveTriggerProps, SelectPrimitiveValueProps, SelectPrimitiveViewportProps, } from "./types";
@@ -0,0 +1,41 @@
1
+ import type React from "react";
2
+ import type { StyleProp, ViewStyle } from "react-native";
3
+ import type { ContentProps, ContentRef, GroupProps, GroupRef, ItemIndicatorProps, ItemIndicatorRef, ItemProps, ItemRef, ItemTextProps, ItemTextRef, LabelProps, LabelRef, Option, OverlayProps, OverlayRef, PortalProps, RootProps, SeparatorProps, SeparatorRef, TriggerProps, TriggerRef, ValueProps, ViewportProps } from "@rn-primitives/select";
4
+ export type { Option as SelectPrimitiveOption };
5
+ export type SelectPrimitiveRootProps = RootProps;
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. */
8
+ readonly testID?: string;
9
+ /** Critical (error) border styling. Style-only — no field/validation logic. */
10
+ readonly invalid?: boolean;
11
+ /** Callback styles are not supported; state styling is composed internally. */
12
+ readonly style?: StyleProp<ViewStyle>;
13
+ readonly children?: React.ReactNode;
14
+ }
15
+ export type SelectPrimitiveValueProps = ValueProps;
16
+ export type SelectPrimitivePortalProps = PortalProps;
17
+ export interface SelectPrimitiveOverlayProps extends Omit<OverlayProps, "style" | "asChild">, React.RefAttributes<OverlayRef> {
18
+ readonly style?: StyleProp<ViewStyle>;
19
+ }
20
+ export interface SelectPrimitiveContentProps extends ContentProps, React.RefAttributes<ContentRef> {
21
+ }
22
+ export type SelectPrimitiveViewportProps = ViewportProps;
23
+ export interface SelectPrimitiveItemProps extends Omit<ItemProps, "style" | "asChild">, React.RefAttributes<ItemRef> {
24
+ /** Callback styles are not supported; pressed styling is composed internally. */
25
+ readonly style?: StyleProp<ViewStyle>;
26
+ /** Compose `ItemText` / `ItemIndicator` (and any other content) as children. */
27
+ readonly children?: React.ReactNode;
28
+ }
29
+ export interface SelectPrimitiveItemTextProps extends Omit<ItemTextProps, "asChild">, React.RefAttributes<ItemTextRef> {
30
+ /** Greys the label; the composing layer forwards the row's disabled state. */
31
+ readonly disabled?: boolean;
32
+ }
33
+ /** The default selected-item checkmark; overridable via `children`. */
34
+ export interface SelectPrimitiveItemIndicatorProps extends ItemIndicatorProps, React.RefAttributes<ItemIndicatorRef> {
35
+ }
36
+ export interface SelectPrimitiveGroupProps extends GroupProps, React.RefAttributes<GroupRef> {
37
+ }
38
+ export interface SelectPrimitiveLabelProps extends LabelProps, React.RefAttributes<LabelRef> {
39
+ }
40
+ export interface SelectPrimitiveSeparatorProps extends SeparatorProps, React.RefAttributes<SeparatorRef> {
41
+ }
@@ -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,10 @@
1
+ import React from "react";
2
+ interface PortalHostWrapperProps {
3
+ readonly children: React.ReactNode;
4
+ }
5
+ /**
6
+ * Test wrapper that mounts a `PortalHost` so portal-based overlays (e.g. the
7
+ * Select dropdown) render: `render(ui, { wrapper: PortalHostWrapper })`.
8
+ */
9
+ export declare function PortalHostWrapper({ children }: PortalHostWrapperProps): React.JSX.Element;
10
+ export {};
@@ -1 +1,2 @@
1
1
  export { MockSafeAreaProvider } from "./MockSafeAreaProvider";
2
+ export { PortalHostWrapper } from "./PortalHostWrapper";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.105.4-addeditor-cab44cf.11+cab44cfa",
3
+ "version": "0.106.0",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -58,6 +58,8 @@
58
58
  "dependencies": {
59
59
  "@react-native-clipboard/clipboard": "^1.11.2",
60
60
  "@react-native-picker/picker": "^2.4.10",
61
+ "@rn-primitives/portal": "^1.4.0",
62
+ "@rn-primitives/select": "^1.4.0",
61
63
  "autolinker": "^4.0.0",
62
64
  "deepmerge": "^4.2.2",
63
65
  "lodash": "^4.17.21",
@@ -71,7 +73,7 @@
71
73
  "devDependencies": {
72
74
  "@babel/runtime": "^7.29.2",
73
75
  "@gorhom/bottom-sheet": "^5.2.8",
74
- "@jobber/design": "0.103.1-addeditor-cab44cf.11+cab44cfa",
76
+ "@jobber/design": "0.105.0",
75
77
  "@jobber/hooks": "2.21.0",
76
78
  "@react-native-community/datetimepicker": "^8.4.5",
77
79
  "@react-native/babel-preset": "^0.82.1",
@@ -122,5 +124,5 @@
122
124
  "react-native-screens": ">=4.18.0",
123
125
  "react-native-svg": ">=12.0.0"
124
126
  },
125
- "gitHead": "cab44cfa1430ab6784636b2d3db8ad771db0999a"
127
+ "gitHead": "b13d0074cac7624ef3b8efa7e79e26697d9e15a6"
126
128
  }
@@ -0,0 +1,5 @@
1
+ // The portal host that backs every portal-based primitive (e.g. the Select
2
+ // dropdown). Apps mount one <AtlantisPortalHost/> at their root; it shares this
3
+ // package's bundled @rn-primitives/portal instance with the primitives' Portal,
4
+ // so registered overlays render in the host.
5
+ export { PortalHost as AtlantisPortalHost } from "@rn-primitives/portal";
@@ -0,0 +1,221 @@
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 { useRootContext } from "@rn-primitives/select";
7
+ import { SelectPrimitive } from "./SelectPrimitive";
8
+ import { Text } from "../../Text";
9
+ import { Icon } from "../../Icon";
10
+
11
+ const meta = {
12
+ title: "Components/Primitives/SelectPrimitive",
13
+ component: SelectPrimitive.Root,
14
+ parameters: {
15
+ viewport: { defaultViewport: "mobile1" },
16
+ },
17
+ decorators: [
18
+ (Story: React.ComponentType) => (
19
+ <>
20
+ <SafeAreaProvider>
21
+ <View style={{ width: 320, padding: 16, minHeight: 480 }}>
22
+ <Story />
23
+ </View>
24
+ </SafeAreaProvider>
25
+ <PortalHost />
26
+ </>
27
+ ),
28
+ ],
29
+ } satisfies Meta<typeof SelectPrimitive.Root>;
30
+ export default meta;
31
+ type Story = StoryObj<typeof meta>;
32
+
33
+ const CITIES = [
34
+ { value: "tor", label: "Toronto" },
35
+ { value: "van", label: "Vancouver" },
36
+ { value: "edm", label: "Edmonton" },
37
+ { value: "wpg", label: "Winnipeg" },
38
+ { value: "hal", label: "Halifax" },
39
+ ];
40
+
41
+ /** Composes an option row from the styled `ItemText` + `ItemIndicator` parts. */
42
+ function Option({
43
+ value,
44
+ label,
45
+ disabled,
46
+ }: {
47
+ readonly value: string;
48
+ readonly label: string;
49
+ readonly disabled?: boolean;
50
+ }) {
51
+ return (
52
+ <SelectPrimitive.Item value={value} label={label} disabled={disabled}>
53
+ <SelectPrimitive.ItemText disabled={disabled} />
54
+ <SelectPrimitive.ItemIndicator>
55
+ <Icon name="checkmark" />
56
+ </SelectPrimitive.ItemIndicator>
57
+ </SelectPrimitive.Item>
58
+ );
59
+ }
60
+
61
+ function CityItems() {
62
+ return (
63
+ <>
64
+ {CITIES.map(city => (
65
+ <Option key={city.value} value={city.value} label={city.label} />
66
+ ))}
67
+ </>
68
+ );
69
+ }
70
+
71
+ /** Composes the raw overlay parts (`Portal > Overlay + Content > Viewport`). */
72
+ function Dropdown({ children }: { readonly children: React.ReactNode }) {
73
+ return (
74
+ <SelectPrimitive.Portal>
75
+ <SelectPrimitive.Overlay />
76
+ <SelectPrimitive.Content>
77
+ <SelectPrimitive.Viewport>{children}</SelectPrimitive.Viewport>
78
+ </SelectPrimitive.Content>
79
+ </SelectPrimitive.Portal>
80
+ );
81
+ }
82
+
83
+ /**
84
+ * A trailing chevron composed onto the trigger. The primitive owns no chevron —
85
+ * the icon and its open-state rotation are composed here (this is what the
86
+ * sugared `Select` will do), reading `open` from the primitive root context.
87
+ */
88
+ function Chevron() {
89
+ const { open } = useRootContext();
90
+
91
+ return (
92
+ <View style={{ transform: [{ rotate: open ? "180deg" : "0deg" }] }}>
93
+ <Icon name="arrowDown" color="iconSecondary" />
94
+ </View>
95
+ );
96
+ }
97
+
98
+ /** Composes the trigger: the value plus a composed, rotating chevron. */
99
+ function TriggerField({
100
+ placeholder,
101
+ invalid,
102
+ }: {
103
+ readonly placeholder: string;
104
+ readonly invalid?: boolean;
105
+ }) {
106
+ return (
107
+ <SelectPrimitive.Trigger invalid={invalid}>
108
+ <SelectPrimitive.Value placeholder={placeholder} />
109
+ <Chevron />
110
+ </SelectPrimitive.Trigger>
111
+ );
112
+ }
113
+
114
+ function StateRow({
115
+ caption,
116
+ children,
117
+ }: {
118
+ readonly caption: string;
119
+ readonly children: React.ReactNode;
120
+ }) {
121
+ return (
122
+ <View style={{ gap: 4 }}>
123
+ <Text level="textSupporting" variation="subdued">
124
+ {caption}
125
+ </Text>
126
+ {children}
127
+ </View>
128
+ );
129
+ }
130
+
131
+ export const Basic: Story = {
132
+ render: () => (
133
+ <SelectPrimitive.Root>
134
+ <TriggerField placeholder="Select an option" />
135
+ <Dropdown>
136
+ <CityItems />
137
+ </Dropdown>
138
+ </SelectPrimitive.Root>
139
+ ),
140
+ };
141
+
142
+ export const WithSelection: Story = {
143
+ render: () => (
144
+ <SelectPrimitive.Root defaultValue={{ value: "van", label: "Vancouver" }}>
145
+ <TriggerField placeholder="Select an option" />
146
+ <Dropdown>
147
+ <CityItems />
148
+ </Dropdown>
149
+ </SelectPrimitive.Root>
150
+ ),
151
+ };
152
+
153
+ export const GroupsAndSeparator: Story = {
154
+ render: () => (
155
+ <SelectPrimitive.Root>
156
+ <TriggerField placeholder="Select a city" />
157
+ <Dropdown>
158
+ <SelectPrimitive.Group>
159
+ <SelectPrimitive.Label>West</SelectPrimitive.Label>
160
+ <Option value="van" label="Vancouver" />
161
+ <Option value="edm" label="Edmonton" />
162
+ </SelectPrimitive.Group>
163
+ <SelectPrimitive.Separator />
164
+ <SelectPrimitive.Group>
165
+ <SelectPrimitive.Label>East</SelectPrimitive.Label>
166
+ <Option value="tor" label="Toronto" />
167
+ <Option value="hal" label="Halifax" />
168
+ </SelectPrimitive.Group>
169
+ </Dropdown>
170
+ </SelectPrimitive.Root>
171
+ ),
172
+ };
173
+
174
+ export const DisabledOption: Story = {
175
+ render: () => (
176
+ <SelectPrimitive.Root>
177
+ <TriggerField placeholder="Select an option" />
178
+ <Dropdown>
179
+ <Option value="tor" label="Toronto" />
180
+ <Option value="van" label="Vancouver" disabled />
181
+ <Option value="edm" label="Edmonton" />
182
+ </Dropdown>
183
+ </SelectPrimitive.Root>
184
+ ),
185
+ };
186
+
187
+ export const TriggerStates: Story = {
188
+ render: () => (
189
+ <View style={{ gap: 24 }}>
190
+ <StateRow caption="Default — tap to open">
191
+ <SelectPrimitive.Root>
192
+ <TriggerField placeholder="Select an option" />
193
+ <Dropdown>
194
+ <CityItems />
195
+ </Dropdown>
196
+ </SelectPrimitive.Root>
197
+ </StateRow>
198
+
199
+ <StateRow caption="Critical (invalid) — tap to open">
200
+ <SelectPrimitive.Root>
201
+ <TriggerField placeholder="Select an option" invalid />
202
+ <Dropdown>
203
+ <CityItems />
204
+ </Dropdown>
205
+ </SelectPrimitive.Root>
206
+ </StateRow>
207
+
208
+ <StateRow caption="Disabled — non-interactive, greyed chevron">
209
+ <SelectPrimitive.Root
210
+ disabled
211
+ defaultValue={{ value: "tor", label: "Toronto" }}
212
+ >
213
+ <TriggerField placeholder="Select an option" />
214
+ <Dropdown>
215
+ <CityItems />
216
+ </Dropdown>
217
+ </SelectPrimitive.Root>
218
+ </StateRow>
219
+ </View>
220
+ ),
221
+ };
@@ -0,0 +1,141 @@
1
+ import { buildThemedStyles } from "../../AtlantisThemeContext";
2
+ import { getTypographyStyles } from "../../Typography";
3
+ import { tokens as staticTokens } from "../../utils/design";
4
+
5
+ const baseLineHeight = staticTokens["typography--lineHeight-base"];
6
+
7
+ export const useStyles = buildThemedStyles(tokens => {
8
+ const typographyStyles = getTypographyStyles(tokens);
9
+
10
+ // The dark theme merges a web CSS-string `shadow-base` over the native object
11
+ // shadow, leaving stray indexed keys on the value. Pick only the React Native
12
+ // shadow properties so those keys never reach the style (spreading them
13
+ // breaks style application through the dropdown's portal on react-native-web).
14
+ const shadowBase = tokens["shadow-base"];
15
+ const contentShadow = {
16
+ shadowColor: shadowBase.shadowColor,
17
+ shadowOffset: shadowBase.shadowOffset,
18
+ shadowOpacity: shadowBase.shadowOpacity,
19
+ shadowRadius: shadowBase.shadowRadius,
20
+ elevation: shadowBase.elevation,
21
+ };
22
+
23
+ return {
24
+ trigger: {
25
+ flexDirection: "row",
26
+ alignItems: "center",
27
+ gap: tokens["space-small"],
28
+ minHeight: tokens["space-largest"] + tokens["space-small"],
29
+ paddingHorizontal: tokens["space-slim"],
30
+ backgroundColor: tokens["color-surface"],
31
+ borderWidth: tokens["border-base"],
32
+ borderColor: tokens["color-border--interactive"],
33
+ borderRadius: tokens["radius-base"],
34
+ },
35
+
36
+ triggerOpen: {
37
+ borderWidth: tokens["border-thick"],
38
+ borderColor: tokens["color-interactive--subtle"],
39
+ // Keep the content from shifting when the border thickens.
40
+ paddingHorizontal:
41
+ tokens["space-slim"] - (tokens["border-thick"] - tokens["border-base"]),
42
+ },
43
+
44
+ triggerPressed: {
45
+ borderWidth: tokens["border-thick"],
46
+ borderColor: tokens["color-border--interactive"],
47
+ paddingHorizontal:
48
+ tokens["space-slim"] - (tokens["border-thick"] - tokens["border-base"]),
49
+ },
50
+
51
+ // Critical sets only the colour; width follows the open/pressed state.
52
+ triggerInvalid: {
53
+ borderColor: tokens["color-critical"],
54
+ },
55
+
56
+ triggerDisabled: {
57
+ backgroundColor: tokens["color-disabled--secondary"],
58
+ },
59
+
60
+ value: {
61
+ flex: 1,
62
+ color: tokens["color-text"],
63
+ fontFamily: typographyStyles.baseRegularRegular.fontFamily,
64
+ fontSize: typographyStyles.defaultSize.fontSize,
65
+ lineHeight: baseLineHeight,
66
+ letterSpacing: typographyStyles.baseLetterSpacing.letterSpacing,
67
+ },
68
+
69
+ valuePlaceholder: {
70
+ color: tokens["color-text--secondary"],
71
+ },
72
+
73
+ valueDisabled: {
74
+ color: tokens["color-disabled"],
75
+ },
76
+
77
+ overlay: {
78
+ position: "absolute",
79
+ top: 0,
80
+ right: 0,
81
+ bottom: 0,
82
+ left: 0,
83
+ },
84
+
85
+ content: {
86
+ backgroundColor: tokens["color-surface"],
87
+ borderWidth: tokens["border-base"],
88
+ borderColor: tokens["color-border"],
89
+ borderRadius: tokens["radius-base"],
90
+ padding: tokens["space-small"],
91
+ ...contentShadow,
92
+ },
93
+
94
+ item: {
95
+ flexDirection: "row",
96
+ alignItems: "center",
97
+ gap: tokens["space-small"],
98
+ minHeight: tokens["space-largest"] + tokens["space-small"],
99
+ padding: tokens["space-small"],
100
+ borderRadius: tokens["radius-small"],
101
+ },
102
+
103
+ itemPressed: {
104
+ backgroundColor: tokens["color-surface--hover"],
105
+ },
106
+
107
+ itemText: {
108
+ flex: 1,
109
+ color: tokens["color-text"],
110
+ fontFamily: typographyStyles.baseRegularSemiBold.fontFamily,
111
+ fontSize: typographyStyles.defaultSize.fontSize,
112
+ lineHeight: baseLineHeight,
113
+ letterSpacing: typographyStyles.baseLetterSpacing.letterSpacing,
114
+ },
115
+
116
+ itemTextDisabled: {
117
+ color: tokens["color-disabled"],
118
+ },
119
+
120
+ itemIndicator: {
121
+ justifyContent: "center",
122
+ alignItems: "center",
123
+ },
124
+
125
+ groupLabel: {
126
+ color: tokens["color-text--secondary"],
127
+ fontFamily: typographyStyles.baseRegularRegular.fontFamily,
128
+ fontSize: tokens["typography--fontSize-small"],
129
+ lineHeight: tokens["typography--lineHeight-tight"],
130
+ paddingTop: tokens["space-small"],
131
+ paddingBottom: tokens["space-smaller"],
132
+ paddingHorizontal: tokens["space-small"],
133
+ },
134
+
135
+ separator: {
136
+ height: tokens["border-base"],
137
+ backgroundColor: tokens["color-border"],
138
+ marginVertical: tokens["space-small"],
139
+ },
140
+ };
141
+ });