@butternutbox/pawprint-native 1.6.3 → 2.0.1

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 (44) hide show
  1. package/.turbo/turbo-build.log +9 -9
  2. package/CHANGELOG.md +61 -0
  3. package/COMPONENT_GUIDELINES.md +57 -1
  4. package/dist/index.cjs +1459 -1380
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +81 -117
  7. package/dist/index.d.ts +81 -117
  8. package/dist/index.js +1206 -1125
  9. package/dist/index.js.map +1 -1
  10. package/package.json +3 -3
  11. package/src/__mocks__/react-native.tsx +33 -5
  12. package/src/__mocks__/rn-primitives/portal.tsx +71 -0
  13. package/src/components/atoms/Input/InputField.tsx +12 -2
  14. package/src/components/molecules/Drawer/Drawer.test.tsx +54 -2
  15. package/src/components/molecules/Drawer/DrawerBody.tsx +2 -17
  16. package/src/components/molecules/Drawer/DrawerContent.tsx +49 -2
  17. package/src/components/molecules/Drawer/DrawerOverlay.tsx +28 -16
  18. package/src/components/molecules/SelectField/AsyncSearch.test.tsx +373 -0
  19. package/src/components/molecules/SelectField/SelectDrawerField.stories.tsx +192 -0
  20. package/src/components/molecules/SelectField/SelectDrawerField.test.tsx +210 -0
  21. package/src/components/molecules/SelectField/SelectDrawerField.tsx +245 -0
  22. package/src/components/molecules/SelectField/SelectField.stories.tsx +221 -421
  23. package/src/components/molecules/SelectField/SelectField.test.tsx +214 -662
  24. package/src/components/molecules/SelectField/SelectField.tsx +269 -539
  25. package/src/components/molecules/SelectField/SelectFieldDropdown.tsx +90 -0
  26. package/src/components/molecules/SelectField/SelectFieldList.tsx +88 -0
  27. package/src/components/molecules/SelectField/SelectFieldOption.tsx +90 -0
  28. package/src/components/molecules/SelectField/core/index.ts +8 -0
  29. package/src/components/molecules/SelectField/core/listHeight.test.ts +22 -0
  30. package/src/components/molecules/SelectField/core/listHeight.ts +8 -0
  31. package/src/components/molecules/SelectField/core/options.ts +35 -0
  32. package/src/components/molecules/SelectField/core/useSelectOptions.ts +116 -0
  33. package/src/components/molecules/SelectField/index.ts +12 -9
  34. package/src/index.ts +0 -1
  35. package/src/test-utils.tsx +5 -1
  36. package/vitest.config.ts +7 -0
  37. package/src/components/molecules/SelectField/SelectFieldContent.tsx +0 -81
  38. package/src/components/molecules/SelectField/SelectFieldItem.tsx +0 -133
  39. package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +0 -188
  40. package/src/components/molecules/SelectField/SelectFieldValue.tsx +0 -33
  41. package/src/components/molecules/SelectField/hooks/index.ts +0 -2
  42. package/src/components/molecules/SelectField/hooks/useSelectField.ts +0 -84
  43. package/src/hooks/index.ts +0 -1
  44. package/src/hooks/useDrawerKeyboardAvoidance.ts +0 -29
@@ -0,0 +1,90 @@
1
+ import React from "react"
2
+ import { ScrollView, View, ViewProps } from "react-native"
3
+ import styled from "@emotion/native"
4
+ import { parseTokenValue } from "../../../utils"
5
+
6
+ export type SelectFieldPlacement = "below" | "above"
7
+
8
+ export type SelectFieldAnchorRect = {
9
+ x: number
10
+ y: number
11
+ width: number
12
+ height: number
13
+ }
14
+
15
+ type SelectFieldDropdownOwnProps = {
16
+ anchor: SelectFieldAnchorRect
17
+ placement: SelectFieldPlacement
18
+ maxHeight: number
19
+ screenHeight: number
20
+ children?: React.ReactNode
21
+ }
22
+
23
+ export type SelectFieldDropdownProps = SelectFieldDropdownOwnProps &
24
+ Omit<ViewProps, keyof SelectFieldDropdownOwnProps>
25
+
26
+ const StyledDropdown = styled(View)<{
27
+ dropdownPlacement: SelectFieldPlacement
28
+ anchorRect: SelectFieldAnchorRect
29
+ screenHeight: number
30
+ }>(({ theme, dropdownPlacement, anchorRect, screenHeight }) => {
31
+ const { dropdown } = theme.tokens.components.dropdownList
32
+ const { spacing } = theme.tokens.components.inputs
33
+ const shadow = dropdown.list.dropshadow.default
34
+ const offset = parseTokenValue(spacing.gap)
35
+
36
+ return {
37
+ position: "absolute",
38
+ left: anchorRect.x,
39
+ width: anchorRect.width,
40
+ ...(dropdownPlacement === "below"
41
+ ? { top: anchorRect.y + anchorRect.height + offset }
42
+ : { bottom: screenHeight - anchorRect.y + offset }),
43
+ minWidth: parseTokenValue(dropdown.list.size.minWidth),
44
+ borderRadius: parseTokenValue(dropdown.list.borderRadius.default),
45
+ shadowColor: shadow.color,
46
+ shadowOffset: {
47
+ width: parseTokenValue(shadow.offsetX),
48
+ height: parseTokenValue(shadow.offsetY)
49
+ },
50
+ shadowOpacity: 1,
51
+ shadowRadius: parseTokenValue(shadow.blur),
52
+ elevation: 8
53
+ }
54
+ })
55
+
56
+ const StyledSurface = styled(View)(({ theme }) => {
57
+ const { dropdown } = theme.tokens.components.dropdownList
58
+ const { colour } = theme.tokens.components.inputs
59
+
60
+ return {
61
+ backgroundColor: colour.field.background.default,
62
+ borderRadius: parseTokenValue(dropdown.list.borderRadius.default),
63
+ overflow: "hidden"
64
+ }
65
+ })
66
+
67
+ export const SelectFieldDropdown = React.forwardRef<
68
+ View,
69
+ SelectFieldDropdownProps
70
+ >(({ anchor, placement, maxHeight, screenHeight, children, ...rest }, ref) => (
71
+ <StyledDropdown
72
+ ref={ref}
73
+ dropdownPlacement={placement}
74
+ anchorRect={anchor}
75
+ screenHeight={screenHeight}
76
+ {...rest}
77
+ >
78
+ <StyledSurface style={{ maxHeight }}>
79
+ <ScrollView
80
+ keyboardShouldPersistTaps="always"
81
+ nestedScrollEnabled
82
+ showsVerticalScrollIndicator
83
+ >
84
+ {children}
85
+ </ScrollView>
86
+ </StyledSurface>
87
+ </StyledDropdown>
88
+ ))
89
+
90
+ SelectFieldDropdown.displayName = "SelectField.Dropdown"
@@ -0,0 +1,88 @@
1
+ import React from "react"
2
+ import { View } from "react-native"
3
+ import styled from "@emotion/native"
4
+ import { Spinner } from "../../atoms/Spinner"
5
+ import { Typography } from "../../atoms/Typography"
6
+ import { parseTokenValue } from "../../../utils"
7
+ import { SelectFieldOption } from "./SelectFieldOption"
8
+ import type { SelectFieldOptionData, SelectOptionsState } from "./core"
9
+
10
+ export type SelectFieldListProps = {
11
+ state: SelectOptionsState
12
+ selectedValue: string | null | undefined
13
+ onSelect: (option: SelectFieldOptionData) => void
14
+ noResultsText: string
15
+ errorText: string
16
+ searchHintText: string
17
+ }
18
+
19
+ const StyledStatusRow = styled(View)(({ theme }) => {
20
+ const { dropdown } = theme.tokens.components.dropdownList
21
+
22
+ return {
23
+ height: parseTokenValue(dropdown.listItem.size.height),
24
+ paddingHorizontal: parseTokenValue(
25
+ dropdown.listItem.spacing.horiztonalPadding
26
+ ),
27
+ alignItems: "center",
28
+ justifyContent: "center"
29
+ }
30
+ })
31
+
32
+ export const SelectFieldList = ({
33
+ state,
34
+ selectedValue,
35
+ onSelect,
36
+ noResultsText,
37
+ errorText,
38
+ searchHintText
39
+ }: SelectFieldListProps) => {
40
+ if (state.needsMoreInput) {
41
+ return (
42
+ <StyledStatusRow>
43
+ <Typography size="sm">{searchHintText}</Typography>
44
+ </StyledStatusRow>
45
+ )
46
+ }
47
+
48
+ if (state.isLoading) {
49
+ return (
50
+ <StyledStatusRow accessibilityLabel="Loading results">
51
+ <Spinner size="sm" />
52
+ </StyledStatusRow>
53
+ )
54
+ }
55
+
56
+ if (state.hasError) {
57
+ return (
58
+ <StyledStatusRow>
59
+ <Typography size="sm">{errorText}</Typography>
60
+ </StyledStatusRow>
61
+ )
62
+ }
63
+
64
+ if (state.options.length === 0) {
65
+ return (
66
+ <StyledStatusRow>
67
+ <Typography size="sm">{noResultsText}</Typography>
68
+ </StyledStatusRow>
69
+ )
70
+ }
71
+
72
+ return (
73
+ <>
74
+ {state.options.map((option) => (
75
+ <SelectFieldOption
76
+ key={option.value}
77
+ label={option.label}
78
+ leadingIcon={option.leadingIcon}
79
+ disabled={option.disabled}
80
+ isSelected={option.value === selectedValue}
81
+ onPress={() => onSelect(option)}
82
+ />
83
+ ))}
84
+ </>
85
+ )
86
+ }
87
+
88
+ SelectFieldList.displayName = "SelectField.List"
@@ -0,0 +1,90 @@
1
+ import React from "react"
2
+ import { Pressable, PressableProps, View } from "react-native"
3
+ import styled from "@emotion/native"
4
+ import { useTheme } from "@emotion/react"
5
+ import { Typography } from "../../atoms/Typography"
6
+ import { parseTokenValue } from "../../../utils"
7
+
8
+ type SelectFieldOptionOwnProps = {
9
+ label: string
10
+ leadingIcon?: React.ReactNode
11
+ disabled?: boolean
12
+ isSelected?: boolean
13
+ }
14
+
15
+ export type SelectFieldOptionProps = SelectFieldOptionOwnProps &
16
+ Omit<PressableProps, keyof SelectFieldOptionOwnProps>
17
+
18
+ const StyledOption = styled(Pressable)(({ theme }) => {
19
+ const { dropdown } = theme.tokens.components.dropdownList
20
+
21
+ return {
22
+ flexDirection: "row",
23
+ alignItems: "center",
24
+ gap: parseTokenValue(dropdown.listItem.content.gap),
25
+ height: parseTokenValue(dropdown.listItem.size.height),
26
+ paddingHorizontal: parseTokenValue(
27
+ dropdown.listItem.spacing.horiztonalPadding
28
+ )
29
+ }
30
+ })
31
+
32
+ const StyledIconSlot = styled(View)({
33
+ flexShrink: 0,
34
+ alignItems: "center",
35
+ justifyContent: "center"
36
+ })
37
+
38
+ const StyledTextSlot = styled(View)({
39
+ flex: 1,
40
+ minWidth: 0
41
+ })
42
+
43
+ export const SelectFieldOption = React.forwardRef<View, SelectFieldOptionProps>(
44
+ (
45
+ { label, leadingIcon, disabled = false, isSelected = false, ...rest },
46
+ ref
47
+ ) => {
48
+ const theme = useTheme()
49
+ const { dropdown } = theme.tokens.components.dropdownList
50
+ const { colour, typography } = dropdown.listItem
51
+
52
+ const backgroundColor = disabled
53
+ ? colour.background.disabled
54
+ : isSelected
55
+ ? colour.background.hover
56
+ : colour.background.default
57
+
58
+ return (
59
+ <StyledOption
60
+ ref={ref}
61
+ disabled={disabled}
62
+ accessibilityRole="button"
63
+ accessibilityState={{ disabled, selected: isSelected }}
64
+ style={{ backgroundColor }}
65
+ {...rest}
66
+ >
67
+ {leadingIcon && <StyledIconSlot>{leadingIcon}</StyledIconSlot>}
68
+ <StyledTextSlot>
69
+ <Typography
70
+ token={
71
+ isSelected
72
+ ? typography.placeholder.hover
73
+ : typography.placeholder.default
74
+ }
75
+ color={
76
+ disabled
77
+ ? colour.text.placeholder.disabled
78
+ : colour.text.placeholder.default
79
+ }
80
+ numberOfLines={1}
81
+ >
82
+ {label}
83
+ </Typography>
84
+ </StyledTextSlot>
85
+ </StyledOption>
86
+ )
87
+ }
88
+ )
89
+
90
+ SelectFieldOption.displayName = "SelectField.Option"
@@ -0,0 +1,8 @@
1
+ export { matchOptions, labelFor } from "./options"
2
+ export type { SelectFieldOptionData } from "./options"
3
+ export { useSelectOptions } from "./useSelectOptions"
4
+ export type {
5
+ SelectOptionsResolver,
6
+ SelectOptionsState
7
+ } from "./useSelectOptions"
8
+ export { drawerListMaxHeight } from "./listHeight"
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { drawerListMaxHeight } from "./listHeight"
3
+
4
+ describe("drawerListMaxHeight", () => {
5
+ it("takes half the window on a phone", () => {
6
+ expect(drawerListMaxHeight(812)).toBe(406)
7
+ })
8
+
9
+ it("caps a tall screen rather than filling it", () => {
10
+ expect(drawerListMaxHeight(1180)).toBe(480)
11
+ expect(drawerListMaxHeight(2000)).toBe(480)
12
+ })
13
+
14
+ it("never exceeds the ceiling", () => {
15
+ expect(drawerListMaxHeight(10000)).toBeLessThanOrEqual(480)
16
+ })
17
+
18
+ it("prefers an explicit override", () => {
19
+ expect(drawerListMaxHeight(1180, 200)).toBe(200)
20
+ expect(drawerListMaxHeight(400, 900)).toBe(900)
21
+ })
22
+ })
@@ -0,0 +1,8 @@
1
+ const LIST_HEIGHT_RATIO = 0.5
2
+ const LIST_HEIGHT_CEILING = 480
3
+
4
+ export const drawerListMaxHeight = (
5
+ windowHeight: number,
6
+ override?: number
7
+ ): number =>
8
+ override ?? Math.min(windowHeight * LIST_HEIGHT_RATIO, LIST_HEIGHT_CEILING)
@@ -0,0 +1,35 @@
1
+ import React from "react"
2
+
3
+ export type SelectFieldOptionData = {
4
+ value: string
5
+ label: string
6
+ leadingIcon?: React.ReactNode
7
+ disabled?: boolean
8
+ }
9
+
10
+ export const matchOptions = (
11
+ options: SelectFieldOptionData[],
12
+ query: string
13
+ ): SelectFieldOptionData[] => {
14
+ const needle = query.trim().toLowerCase()
15
+ if (needle.length === 0) return options
16
+
17
+ const startsWith: SelectFieldOptionData[] = []
18
+ const contains: SelectFieldOptionData[] = []
19
+
20
+ options.forEach((option) => {
21
+ const haystack = option.label.toLowerCase()
22
+ if (haystack.startsWith(needle)) {
23
+ startsWith.push(option)
24
+ } else if (haystack.includes(needle)) {
25
+ contains.push(option)
26
+ }
27
+ })
28
+
29
+ return [...startsWith, ...contains]
30
+ }
31
+
32
+ export const labelFor = (
33
+ options: SelectFieldOptionData[],
34
+ value: string | null | undefined
35
+ ): string => options.find((option) => option.value === value)?.label ?? ""
@@ -0,0 +1,116 @@
1
+ import React from "react"
2
+ import { matchOptions } from "./options"
3
+ import type { SelectFieldOptionData } from "./options"
4
+
5
+ export type SelectOptionsResolver = (
6
+ query: string
7
+ ) => Promise<SelectFieldOptionData[]>
8
+
9
+ type UseSelectOptionsArgs = {
10
+ options: SelectFieldOptionData[]
11
+ query: string
12
+ onSearchQuery?: SelectOptionsResolver
13
+ debounceMs: number
14
+ minQueryLength: number
15
+ enabled: boolean
16
+ }
17
+
18
+ export type SelectOptionsState = {
19
+ options: SelectFieldOptionData[]
20
+ isLoading: boolean
21
+ hasError: boolean
22
+ needsMoreInput: boolean
23
+ }
24
+
25
+ export const useSelectOptions = ({
26
+ options,
27
+ query,
28
+ onSearchQuery,
29
+ debounceMs,
30
+ minQueryLength,
31
+ enabled
32
+ }: UseSelectOptionsArgs): SelectOptionsState => {
33
+ const isQueryMode = enabled && typeof onSearchQuery === "function"
34
+ const trimmedQuery = query.trim()
35
+ const meetsMinimum = trimmedQuery.length >= minQueryLength
36
+ const isQueryActive = isQueryMode && trimmedQuery.length > 0 && meetsMinimum
37
+ const needsMoreInput = isQueryMode && !meetsMinimum
38
+
39
+ const [results, setResults] = React.useState<SelectFieldOptionData[]>([])
40
+ const [isLoading, setIsLoading] = React.useState(false)
41
+ const [hasError, setHasError] = React.useState(false)
42
+
43
+ const resolverRef = React.useRef(onSearchQuery)
44
+ React.useEffect(() => {
45
+ resolverRef.current = onSearchQuery
46
+ })
47
+
48
+ const requestIdRef = React.useRef(0)
49
+
50
+ React.useEffect(() => {
51
+ if (!isQueryMode) return
52
+
53
+ if (trimmedQuery.length === 0 || trimmedQuery.length < minQueryLength) {
54
+ requestIdRef.current += 1
55
+ setResults([])
56
+ setIsLoading(false)
57
+ setHasError(false)
58
+ return
59
+ }
60
+
61
+ const requestId = requestIdRef.current + 1
62
+ requestIdRef.current = requestId
63
+ setIsLoading(true)
64
+ setHasError(false)
65
+
66
+ const timer = setTimeout(() => {
67
+ const resolve = resolverRef.current
68
+ if (!resolve) {
69
+ setIsLoading(false)
70
+ return
71
+ }
72
+
73
+ Promise.resolve(resolve(trimmedQuery))
74
+ .then((items) => {
75
+ if (requestIdRef.current !== requestId) return
76
+ setResults(items)
77
+ setIsLoading(false)
78
+ })
79
+ .catch(() => {
80
+ if (requestIdRef.current !== requestId) return
81
+ setResults([])
82
+ setHasError(true)
83
+ setIsLoading(false)
84
+ })
85
+ }, debounceMs)
86
+
87
+ return () => clearTimeout(timer)
88
+ }, [isQueryMode, trimmedQuery, debounceMs, minQueryLength])
89
+
90
+ const localOptions = React.useMemo(
91
+ () => (enabled ? matchOptions(options, query) : options),
92
+ [enabled, options, query]
93
+ )
94
+
95
+ if (needsMoreInput) {
96
+ const hasDefaultList = trimmedQuery.length === 0 && options.length > 0
97
+
98
+ return {
99
+ options: hasDefaultList ? options : [],
100
+ isLoading: false,
101
+ hasError: false,
102
+ needsMoreInput: !hasDefaultList
103
+ }
104
+ }
105
+
106
+ if (!isQueryActive) {
107
+ return {
108
+ options: localOptions,
109
+ isLoading: false,
110
+ hasError: false,
111
+ needsMoreInput: false
112
+ }
113
+ }
114
+
115
+ return { options: results, isLoading, hasError, needsMoreInput: false }
116
+ }
@@ -1,10 +1,13 @@
1
1
  export { SelectField } from "./SelectField"
2
- export type { SelectFieldProps, SelectFieldSearchResult } from "./SelectField"
3
- export { useSelectField } from "./hooks"
4
- export type { SelectValidationRule } from "./hooks"
5
- export { SelectFieldTrigger } from "./SelectFieldTrigger"
6
- export type { SelectFieldTriggerProps } from "./SelectFieldTrigger"
7
- export { SelectFieldValue } from "./SelectFieldValue"
8
- export { SelectFieldContent } from "./SelectFieldContent"
9
- export { SelectFieldItem } from "./SelectFieldItem"
10
- export type { SelectFieldItemProps } from "./SelectFieldItem"
2
+ export type { SelectFieldProps } from "./SelectField"
3
+ export { SelectDrawerField } from "./SelectDrawerField"
4
+ export type { SelectDrawerFieldProps } from "./SelectDrawerField"
5
+ export { SelectFieldOption } from "./SelectFieldOption"
6
+ export type { SelectFieldOptionProps } from "./SelectFieldOption"
7
+ export { SelectFieldDropdown } from "./SelectFieldDropdown"
8
+ export type {
9
+ SelectFieldDropdownProps,
10
+ SelectFieldPlacement
11
+ } from "./SelectFieldDropdown"
12
+ export { matchOptions, labelFor } from "./core"
13
+ export type { SelectFieldOptionData } from "./core"
package/src/index.ts CHANGED
@@ -7,4 +7,3 @@ export { default as styled } from "@emotion/native"
7
7
  export * from "@butternutbox/pawprint-fonts"
8
8
  export * from "./theme"
9
9
  export * from "./components"
10
- export * from "./hooks"
@@ -1,6 +1,7 @@
1
1
  import React from "react"
2
2
  import { render, type RenderOptions } from "@testing-library/react"
3
3
  import { ThemeProvider } from "@emotion/react"
4
+ import { PortalHost } from "@rn-primitives/portal"
4
5
  import { createPawprintTheme } from "./theme/utils"
5
6
 
6
7
  const theme = createPawprintTheme()
@@ -11,7 +12,10 @@ export function renderWithTheme(
11
12
  ) {
12
13
  return render(ui, {
13
14
  wrapper: ({ children }) => (
14
- <ThemeProvider theme={theme}>{children}</ThemeProvider>
15
+ <ThemeProvider theme={theme}>
16
+ {children}
17
+ <PortalHost />
18
+ </ThemeProvider>
15
19
  ),
16
20
  ...options
17
21
  })
package/vitest.config.ts CHANGED
@@ -66,6 +66,13 @@ export default defineConfig({
66
66
  "src/__mocks__/rn-primitives/select.tsx"
67
67
  )
68
68
  },
69
+ {
70
+ find: /^@rn-primitives\/portal$/,
71
+ replacement: resolve(
72
+ __dirname,
73
+ "src/__mocks__/rn-primitives/portal.tsx"
74
+ )
75
+ },
69
76
  {
70
77
  find: /^@rn-primitives\/slot$/,
71
78
  replacement: resolve(__dirname, "src/__mocks__/rn-primitives/slot.tsx")
@@ -1,81 +0,0 @@
1
- import React from "react"
2
- import { View, ViewProps, ScrollView, StyleSheet } from "react-native"
3
- import styled from "@emotion/native"
4
- import { useTheme } from "@emotion/react"
5
- import * as SelectPrimitive from "@rn-primitives/select"
6
- import { Slot } from "@rn-primitives/slot"
7
-
8
- type SelectFieldContentProps = {
9
- children?: React.ReactNode
10
- } & Omit<ViewProps, "children">
11
-
12
- const parseTokenValue = (value: string): number => parseFloat(value)
13
-
14
- const StyledContentShadow = styled(View)(({ theme }) => {
15
- const { dropdown } = theme.tokens.components.dropdownList
16
- const shadow = dropdown.list.dropshadow.default
17
-
18
- return {
19
- borderRadius: parseTokenValue(dropdown.list.borderRadius.default),
20
- shadowColor: shadow.color,
21
- shadowOffset: {
22
- width: parseTokenValue(shadow.offsetX),
23
- height: parseTokenValue(shadow.offsetY)
24
- },
25
- shadowOpacity: 1,
26
- shadowRadius: parseTokenValue(shadow.blur),
27
- elevation: 8
28
- }
29
- })
30
-
31
- const StyledContentInner = styled(View)(({ theme }) => {
32
- const { dropdown } = theme.tokens.components.dropdownList
33
- const { colour } = theme.tokens.components.inputs
34
-
35
- return {
36
- backgroundColor: colour.field.background.default,
37
- borderRadius: parseTokenValue(dropdown.list.borderRadius.default),
38
- minWidth: parseTokenValue(dropdown.list.size.minWidth),
39
- maxHeight: parseTokenValue(dropdown.list.size.maxHeight),
40
- overflow: "hidden"
41
- }
42
- })
43
-
44
- export const SelectFieldContent = React.forwardRef<
45
- View,
46
- SelectFieldContentProps
47
- >(({ children, ...rest }, ref) => {
48
- const theme = useTheme()
49
- const { spacing } = theme.tokens.components.inputs
50
-
51
- return (
52
- <SelectPrimitive.Portal>
53
- <SelectPrimitive.Overlay style={StyleSheet.absoluteFill} />
54
- <SelectPrimitive.Content
55
- side="bottom"
56
- align="start"
57
- sideOffset={parseTokenValue(spacing.gap)}
58
- alignOffset={0}
59
- asChild
60
- >
61
- <Slot ref={ref}>
62
- <StyledContentShadow {...rest}>
63
- <StyledContentInner>
64
- <SelectPrimitive.Viewport>
65
- <ScrollView
66
- showsVerticalScrollIndicator
67
- nestedScrollEnabled
68
- keyboardShouldPersistTaps="handled"
69
- >
70
- {children}
71
- </ScrollView>
72
- </SelectPrimitive.Viewport>
73
- </StyledContentInner>
74
- </StyledContentShadow>
75
- </Slot>
76
- </SelectPrimitive.Content>
77
- </SelectPrimitive.Portal>
78
- )
79
- })
80
-
81
- SelectFieldContent.displayName = "SelectField.Content"