@telus-uds/components-base 1.60.0 → 1.62.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 (66) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/component-docs.json +306 -116
  3. package/lib/Autocomplete/Autocomplete.js +483 -0
  4. package/lib/Autocomplete/Loading.js +51 -0
  5. package/lib/Autocomplete/Suggestions.js +85 -0
  6. package/lib/Autocomplete/constants.js +14 -0
  7. package/lib/Autocomplete/dictionary.js +19 -0
  8. package/lib/Autocomplete/index.js +13 -0
  9. package/lib/IconButton/IconButton.js +28 -9
  10. package/lib/Listbox/GroupControl.js +121 -0
  11. package/lib/Listbox/Listbox.js +198 -0
  12. package/lib/Listbox/ListboxGroup.js +142 -0
  13. package/lib/Listbox/ListboxItem.js +97 -0
  14. package/lib/Listbox/ListboxOverlay.js +106 -0
  15. package/lib/Listbox/PressableItem.js +0 -2
  16. package/lib/Listbox/index.js +5 -24
  17. package/lib/SideNav/Item.js +7 -15
  18. package/lib/TextInput/TextInputBase.js +8 -1
  19. package/lib/Tooltip/Tooltip.js +5 -1
  20. package/lib/Tooltip/Tooltip.native.js +5 -1
  21. package/lib/Tooltip/shared.js +5 -0
  22. package/lib/index.js +17 -13
  23. package/lib/utils/useOverlaidPosition.js +6 -4
  24. package/lib-module/Autocomplete/Autocomplete.js +448 -0
  25. package/lib-module/Autocomplete/Loading.js +36 -0
  26. package/lib-module/Autocomplete/Suggestions.js +66 -0
  27. package/lib-module/Autocomplete/constants.js +4 -0
  28. package/lib-module/Autocomplete/dictionary.js +12 -0
  29. package/lib-module/Autocomplete/index.js +2 -0
  30. package/lib-module/IconButton/IconButton.js +30 -10
  31. package/lib-module/Listbox/GroupControl.js +102 -0
  32. package/lib-module/Listbox/Listbox.js +172 -0
  33. package/lib-module/Listbox/ListboxGroup.js +117 -0
  34. package/lib-module/Listbox/ListboxItem.js +71 -0
  35. package/lib-module/Listbox/ListboxOverlay.js +80 -0
  36. package/lib-module/Listbox/PressableItem.js +0 -2
  37. package/lib-module/Listbox/index.js +2 -2
  38. package/lib-module/SideNav/Item.js +7 -15
  39. package/lib-module/TextInput/TextInputBase.js +8 -1
  40. package/lib-module/Tooltip/Tooltip.js +5 -1
  41. package/lib-module/Tooltip/Tooltip.native.js +5 -1
  42. package/lib-module/Tooltip/shared.js +5 -0
  43. package/lib-module/index.js +2 -1
  44. package/lib-module/utils/useOverlaidPosition.js +5 -4
  45. package/package.json +4 -2
  46. package/src/Autocomplete/Autocomplete.jsx +411 -0
  47. package/src/Autocomplete/Loading.jsx +18 -0
  48. package/src/Autocomplete/Suggestions.jsx +54 -0
  49. package/src/Autocomplete/constants.js +4 -0
  50. package/src/Autocomplete/dictionary.js +12 -0
  51. package/src/Autocomplete/index.js +3 -0
  52. package/src/IconButton/IconButton.jsx +62 -35
  53. package/src/Listbox/GroupControl.jsx +93 -0
  54. package/src/Listbox/Listbox.jsx +165 -0
  55. package/src/Listbox/ListboxGroup.jsx +120 -0
  56. package/src/Listbox/ListboxItem.jsx +76 -0
  57. package/src/Listbox/ListboxOverlay.jsx +82 -0
  58. package/src/Listbox/PressableItem.jsx +0 -2
  59. package/src/Listbox/index.js +3 -2
  60. package/src/SideNav/Item.jsx +7 -13
  61. package/src/TextInput/TextInputBase.jsx +4 -1
  62. package/src/Tooltip/Tooltip.jsx +15 -2
  63. package/src/Tooltip/Tooltip.native.jsx +15 -2
  64. package/src/Tooltip/shared.js +4 -0
  65. package/src/index.js +2 -1
  66. package/src/utils/useOverlaidPosition.js +6 -5
@@ -18,20 +18,30 @@ import {
18
18
  } from '../utils'
19
19
  import Icon from '../Icon'
20
20
 
21
+ // function to get the dimentionals of the conditionals
22
+ const getPasswordDimensions = (password, width, height) => {
23
+ return password ? { width, height } : {}
24
+ }
25
+
21
26
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
22
27
 
23
- const selectOuterStyle = ({
24
- backgroundColor,
25
- outerBorderWidth,
26
- outerBorderColor,
27
- outerBorderGap,
28
- borderRadius,
29
- borderTopLeftRadius,
30
- borderTopRightRadius,
31
- borderBottomLeftRadius,
32
- borderBottomRightRadius,
33
- shadow
34
- }) => [
28
+ const selectOuterStyle = (
29
+ {
30
+ backgroundColor,
31
+ outerBorderWidth,
32
+ outerBorderColor,
33
+ outerBorderGap,
34
+ borderRadius,
35
+ borderTopLeftRadius,
36
+ borderTopRightRadius,
37
+ borderBottomLeftRadius,
38
+ borderBottomRightRadius,
39
+ shadow,
40
+ width,
41
+ height
42
+ },
43
+ password
44
+ ) => [
35
45
  {
36
46
  backgroundColor,
37
47
  ...applyShadowToken(shadow),
@@ -45,31 +55,44 @@ const selectOuterStyle = ({
45
55
  outerBorderColor,
46
56
  outerBorderGap
47
57
  }),
48
- ...Platform.select({ web: { outline: 'none', display: 'inline-flex' } })
58
+ ...getPasswordDimensions(password, width, height),
59
+ ...Platform.select({
60
+ web: {
61
+ outline: 'none',
62
+ display: 'inline-flex',
63
+ alignItems: 'center',
64
+ justifyContent: 'center'
65
+ }
66
+ })
49
67
  },
50
68
  staticStyles.outer
51
69
  ]
52
70
 
53
71
  const calculatePadding = (padding, borderWidth) => padding && Math.max(0, padding - borderWidth) // Stable size as border changes
54
72
 
55
- const selectInnerStyle = ({
56
- borderColor,
57
- borderWidth,
58
- borderTopLeftRadius,
59
- borderTopRightRadius,
60
- borderBottomLeftRadius,
61
- borderBottomRightRadius,
62
- borderRadius,
63
- padding = 0,
64
- borderTopWidth,
65
- borderRightWidth,
66
- borderBottomWidth,
67
- borderLeftWidth,
68
- paddingLeft,
69
- paddingRight,
70
- paddingTop,
71
- paddingBottom
72
- }) => ({
73
+ const selectInnerStyle = (
74
+ {
75
+ borderColor,
76
+ borderWidth,
77
+ borderTopLeftRadius,
78
+ borderTopRightRadius,
79
+ borderBottomLeftRadius,
80
+ borderBottomRightRadius,
81
+ borderRadius,
82
+ padding = 0,
83
+ borderTopWidth,
84
+ borderRightWidth,
85
+ borderBottomWidth,
86
+ borderLeftWidth,
87
+ paddingLeft,
88
+ paddingRight,
89
+ paddingTop,
90
+ paddingBottom,
91
+ width,
92
+ height
93
+ },
94
+ password
95
+ ) => ({
73
96
  // Inner borders animate with the icon and should be treated like a themable feature of the icon
74
97
  borderColor,
75
98
  borderRadius,
@@ -89,9 +112,13 @@ const selectInnerStyle = ({
89
112
  paddingBottom: calculatePadding(paddingBottom, borderBottomWidth),
90
113
  ...Platform.select({
91
114
  web: {
92
- pointerEvents: 'none'
115
+ pointerEvents: 'none',
116
+ display: 'inline-flex',
117
+ alignItems: 'center',
118
+ justifyContent: 'center'
93
119
  }
94
- })
120
+ }),
121
+ ...getPasswordDimensions(password, width, height)
95
122
  })
96
123
 
97
124
  /**
@@ -124,7 +151,7 @@ const IconButton = forwardRef(
124
151
 
125
152
  const getTokens = useThemeTokensCallback('IconButton', tokens, variant)
126
153
  const getOuterStyle = (pressableState) =>
127
- selectOuterStyle(getTokens(resolvePressableState(pressableState)))
154
+ selectOuterStyle(getTokens(resolvePressableState(pressableState), variant.password))
128
155
 
129
156
  return (
130
157
  <Pressable
@@ -139,7 +166,7 @@ const IconButton = forwardRef(
139
166
  {(pressableState) => {
140
167
  const themeTokens = getTokens(resolvePressableState(pressableState))
141
168
  return (
142
- <View style={selectInnerStyle(themeTokens)}>
169
+ <View style={selectInnerStyle(themeTokens, variant.password)}>
143
170
  <Icon
144
171
  icon={IconComponent || themeTokens.icon}
145
172
  title={selectedProps.accessibilityLabel}
@@ -0,0 +1,93 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { View, StyleSheet, Text } from 'react-native'
4
+ import { useThemeTokens } from '../ThemeProvider'
5
+ import Icon from '../Icon'
6
+ import Spacer from '../Spacer'
7
+ import { useListboxContext } from './ListboxContext'
8
+
9
+ const styles = StyleSheet.create({
10
+ controlWrapper: {
11
+ width: '100%',
12
+ flex: 1,
13
+ alignItems: 'center',
14
+ flexDirection: 'row',
15
+ justifyContent: 'space-between',
16
+ boxSizing: 'border-box'
17
+ }
18
+ })
19
+
20
+ const GroupControl = ({ expanded, pressed, hover, focus, label, id }) => {
21
+ const { selectedId, setSelectedId } = useListboxContext()
22
+ const tokens = useThemeTokens(
23
+ 'Listbox',
24
+ {},
25
+ {},
26
+ {
27
+ expanded,
28
+ pressed,
29
+ hover,
30
+ current: selectedId === id && id !== undefined,
31
+ focus
32
+ }
33
+ )
34
+ const {
35
+ groupFontName,
36
+ groupFontWeight,
37
+ groupFontSize,
38
+ groupColor,
39
+ groupBackgroundColor,
40
+ groupBorderColor,
41
+ groupBorderWidth,
42
+ groupBorderRadius,
43
+ groupPaddingLeft,
44
+ groupPaddingRight,
45
+ groupPaddingTop,
46
+ groupPaddingBottom,
47
+ itemTextDecoration,
48
+ itemOutline,
49
+ groupHeight
50
+ } = tokens
51
+ return (
52
+ <View
53
+ onPress={() => setSelectedId(id)}
54
+ style={[
55
+ styles.controlWrapper,
56
+ {
57
+ fontFamily: `${groupFontName}${groupFontWeight}normal`,
58
+ fontSize: groupFontSize,
59
+ color: groupColor,
60
+ textDecoration: itemTextDecoration,
61
+ backgroundColor: groupBackgroundColor,
62
+ outline: itemOutline,
63
+ height: groupHeight,
64
+ border: `${groupBorderWidth}px solid ${groupBorderColor}`,
65
+ borderRadius: groupBorderRadius,
66
+ paddingLeft: groupPaddingLeft - groupBorderWidth,
67
+ paddingRight: groupPaddingRight - groupBorderWidth,
68
+ paddingTop: groupPaddingTop - groupBorderWidth,
69
+ paddingBottom: groupPaddingBottom - groupBorderWidth
70
+ }
71
+ ]}
72
+ >
73
+ <Text>{label}</Text>
74
+ <Spacer space={1} direction="row" />
75
+ <Icon
76
+ icon={tokens.groupIcon}
77
+ tokens={{ color: tokens.groupColor }}
78
+ variant={{ size: 'micro' }}
79
+ />
80
+ </View>
81
+ )
82
+ }
83
+
84
+ GroupControl.propTypes = {
85
+ id: PropTypes.string,
86
+ expanded: PropTypes.bool,
87
+ pressed: PropTypes.bool,
88
+ hover: PropTypes.bool,
89
+ focus: PropTypes.bool,
90
+ label: PropTypes.string
91
+ }
92
+
93
+ export default GroupControl
@@ -0,0 +1,165 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { View, StyleSheet, Platform } from 'react-native'
4
+ import { useThemeTokens } from '../ThemeProvider'
5
+ import { withLinkRouter, getTokensPropType } from '../utils'
6
+ import ExpandCollapse from '../ExpandCollapse'
7
+ import ListboxGroup from './ListboxGroup'
8
+ import ListboxItem from './ListboxItem'
9
+ import { ListboxContext } from './ListboxContext'
10
+ import DropdownOverlay from './ListboxOverlay'
11
+
12
+ const styles = StyleSheet.create({
13
+ list: {
14
+ padding: 0,
15
+ margin: 0
16
+ }
17
+ })
18
+
19
+ const getInitialOpen = (items, selectedId) =>
20
+ items
21
+ .filter(
22
+ (item) =>
23
+ item.items &&
24
+ item.items.some((nestedItem) => (nestedItem.id ?? nestedItem.label) === selectedId)
25
+ )
26
+ .map((item) => item.id ?? item.label)
27
+
28
+ const Listbox = ({
29
+ items = [],
30
+ firstItemRef = null, // focus will be moved to this one once within the menu
31
+ parentRef = null, // to return focus to after leaving the last menu item
32
+ selectedId: defaultSelectedId,
33
+ LinkRouter,
34
+ itemRouterProps,
35
+ onClose,
36
+ variant,
37
+ tokens
38
+ }) => {
39
+ const initialOpen = getInitialOpen(items, defaultSelectedId)
40
+
41
+ const [selectedId, setSelectedId] = useState(defaultSelectedId)
42
+
43
+ const { minHeight, minWidth } = useThemeTokens('Listbox', variant, tokens)
44
+
45
+ // We need to keep track of each item's ref in order to be able to
46
+ // focus on a specific item via keyboard navigation
47
+ const itemRefs = useRef([])
48
+ if (firstItemRef?.current) itemRefs.current[0] = firstItemRef.current
49
+ const [focusedIndex, setFocusedIndex] = useState(0)
50
+ const handleKeydown = useCallback(
51
+ (event) => {
52
+ const nextItemRef = itemRefs.current[focusedIndex + 1]
53
+ const prevItemRef = itemRefs.current[focusedIndex - 1]
54
+ if (event.key === 'ArrowUp' || (event.shiftKey && event.key === 'Tab')) {
55
+ // Move the focus to the previous item or to the parent one if on the first
56
+ if (prevItemRef) {
57
+ event.preventDefault()
58
+ prevItemRef.focus()
59
+ } else if (parentRef) parentRef.current?.focus()
60
+ setFocusedIndex(focusedIndex - 1)
61
+ } else if ((event.key === 'ArrowDown' || event.key === 'Tab') && nextItemRef) {
62
+ event.preventDefault()
63
+ setFocusedIndex(focusedIndex + 1)
64
+ nextItemRef.focus()
65
+ } else if (event.key === 'Escape') {
66
+ // Close the dropdown
67
+ parentRef?.current?.click()
68
+ // Return focus to the dropdown control after leaving the last item
69
+ parentRef?.current?.focus()
70
+ if (onClose) onClose(event)
71
+ }
72
+ },
73
+ [focusedIndex, onClose, parentRef]
74
+ )
75
+
76
+ // Add listeners for mouse clicks outside and for key presses
77
+ useEffect(() => {
78
+ if (Platform.OS === 'web') {
79
+ window.addEventListener('click', onClose)
80
+ window.addEventListener('keydown', handleKeydown)
81
+ window.addEventListener('touchstart', onClose)
82
+ return () => {
83
+ window.removeEventListener('click', onClose)
84
+ window.removeEventListener('keydown', handleKeydown)
85
+ window.removeEventListener('touchstart', onClose)
86
+ }
87
+ }
88
+ return () => {}
89
+ }, [onClose, handleKeydown])
90
+
91
+ return (
92
+ <ListboxContext.Provider value={{ selectedId, setSelectedId }}>
93
+ <ExpandCollapse initialOpen={initialOpen} maxOpen={1}>
94
+ {(expandProps) => (
95
+ <View style={[styles.list, { minHeight, minWidth }]} role="listbox">
96
+ {items.map((item, index) => {
97
+ const { id, label, items: nestedItems } = item
98
+ const itemId = id ?? label
99
+
100
+ // Give the list of refs.
101
+ const itemRef = (ref) => {
102
+ itemRefs.current[index] = ref
103
+ return ref
104
+ }
105
+
106
+ return nestedItems ? (
107
+ <ListboxGroup
108
+ {...item}
109
+ expandProps={expandProps}
110
+ LinkRouter={LinkRouter}
111
+ itemRouterProps={itemRouterProps}
112
+ prevItemRef={itemRefs.current[index - 1] ?? null}
113
+ nextItemRef={itemRefs.current[index + 1] ?? null}
114
+ ref={index === 0 ? firstItemRef : itemRef}
115
+ key={itemId}
116
+ />
117
+ ) : (
118
+ <ListboxItem
119
+ {...item}
120
+ key={itemId}
121
+ id={itemId}
122
+ LinkRouter={LinkRouter}
123
+ itemRouterProps={itemRouterProps}
124
+ prevItemRef={itemRefs.current[index - 1] ?? null}
125
+ nextItemRef={itemRefs.current[index + 1] ?? null}
126
+ ref={index === 0 ? firstItemRef : itemRef}
127
+ />
128
+ )
129
+ })}
130
+ </View>
131
+ )}
132
+ </ExpandCollapse>
133
+ </ListboxContext.Provider>
134
+ )
135
+ }
136
+
137
+ Listbox.propTypes = {
138
+ ...withLinkRouter.propTypes,
139
+ /**
140
+ * Focus will be moved to the item with this ref once within the menu.
141
+ */
142
+ firstItemRef: PropTypes.object,
143
+ /**
144
+ * Focus will be returned to the dropdown control with this ref after leaving
145
+ * the last menu item.
146
+ */
147
+ parentRef: PropTypes.object,
148
+ /**
149
+ * `Listbox` items
150
+ */
151
+ items: PropTypes.array,
152
+ /**
153
+ * To select an item by default
154
+ */
155
+ selectedId: PropTypes.string,
156
+ /**
157
+ * onClose event
158
+ */
159
+ onClose: PropTypes.func,
160
+ tokens: getTokensPropType('Listbox')
161
+ }
162
+
163
+ Listbox.Overlay = DropdownOverlay
164
+
165
+ export default Listbox
@@ -0,0 +1,120 @@
1
+ /* eslint-disable react-native-a11y/has-valid-accessibility-role */
2
+ import React, { forwardRef } from 'react'
3
+ import PropTypes from 'prop-types'
4
+ import { View, StyleSheet } from 'react-native'
5
+ import { withLinkRouter } from '../utils'
6
+ import ExpandCollapse from '../ExpandCollapse'
7
+ import ListboxItem from './ListboxItem'
8
+ import { useListboxContext } from './ListboxContext'
9
+ import GroupControl from './GroupControl'
10
+
11
+ const styles = StyleSheet.create({
12
+ groupWrapper: {
13
+ margin: 0,
14
+ padding: 0,
15
+ overflow: 'hidden'
16
+ },
17
+ list: {
18
+ margin: 0,
19
+ padding: 0
20
+ }
21
+ })
22
+
23
+ const ListboxGroup = forwardRef(
24
+ (
25
+ {
26
+ id,
27
+ label,
28
+ items,
29
+ LinkRouter,
30
+ linkRouterProps,
31
+ expandProps,
32
+ onLastItemBlur,
33
+ nextItemRef,
34
+ prevItemRef
35
+ },
36
+ ref
37
+ ) => {
38
+ const { selectedId } = useListboxContext()
39
+
40
+ // TODO: implement keyboard navigation via refs for grouped items separately here
41
+ return (
42
+ <View id="test" style={styles.groupWrapper} accessibilityRole="listitem">
43
+ <ExpandCollapse.Panel
44
+ panelId={id ?? label}
45
+ controlTokens={{
46
+ icon: null,
47
+ paddingLeft: 0,
48
+ paddingRight: 0,
49
+ paddingTop: 0,
50
+ paddingBottom: 0,
51
+ backgroundColor: 'transparent',
52
+ borderColor: 'transparent',
53
+ textLine: 'none',
54
+ borderWidth: 0
55
+ }}
56
+ // TODO refactor
57
+ // eslint-disable-next-line react/no-unstable-nested-components
58
+ control={(controlProps) => (
59
+ <GroupControl id={id ?? label} {...controlProps} label={label} />
60
+ )}
61
+ {...expandProps}
62
+ tokens={{
63
+ contentPaddingLeft: 0,
64
+ contentPaddingRight: 0,
65
+ contentPaddingTop: 0,
66
+ contentPaddingBottom: 0,
67
+ borderColor: 'transparent',
68
+ borderRadius: 0,
69
+ borderWidth: 0,
70
+ marginBottom: 0
71
+ }}
72
+ controlRef={ref}
73
+ >
74
+ <View style={styles.list}>
75
+ {items.map((item, index) => {
76
+ return (
77
+ <ListboxItem
78
+ key={item.label}
79
+ id={item.id ?? item.label}
80
+ {...item}
81
+ selected={
82
+ (item.id && item.id === selectedId) || (item.label && item.label === selectedId)
83
+ }
84
+ isChild
85
+ LinkRouter={LinkRouter}
86
+ linkRouterProps={linkRouterProps}
87
+ {...(index === 0 && { prevItemRef })}
88
+ {...(index === items.length - 1 && { nextItemRef })}
89
+ {...(index === items.length - 1 && { onBlur: onLastItemBlur })}
90
+ />
91
+ )
92
+ })}
93
+ </View>
94
+ </ExpandCollapse.Panel>
95
+ </View>
96
+ )
97
+ }
98
+ )
99
+ ListboxGroup.displayName = 'ListboxGroup'
100
+
101
+ ListboxGroup.propTypes = {
102
+ ...withLinkRouter.propTypes,
103
+ label: PropTypes.string,
104
+ items: PropTypes.arrayOf(
105
+ PropTypes.shape({
106
+ href: PropTypes.string,
107
+ label: PropTypes.string,
108
+ current: PropTypes.bool
109
+ })
110
+ ),
111
+ expandProps: PropTypes.object,
112
+ nextItemRef: PropTypes.object,
113
+ prevItemRef: PropTypes.object,
114
+ /**
115
+ * Use this callback to redirect the focus after it leaves the last item of the group.
116
+ */
117
+ onLastItemBlur: PropTypes.func
118
+ }
119
+
120
+ export default ListboxGroup
@@ -0,0 +1,76 @@
1
+ /* eslint-disable react/require-default-props */
2
+ import React, { forwardRef } from 'react'
3
+ import PropTypes from 'prop-types'
4
+ import { View, StyleSheet } from 'react-native'
5
+ import { selectSystemProps, withLinkRouter, htmlAttrs } from '../utils'
6
+ import PressableItem from './PressableItem'
7
+ import { useThemeTokensCallback } from '../ThemeProvider'
8
+
9
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
10
+
11
+ const styles = StyleSheet.create({
12
+ itemContainer: {
13
+ display: 'flex',
14
+ margin: 0
15
+ },
16
+ childContainer: {
17
+ paddingLeft: 16
18
+ }
19
+ })
20
+
21
+ const ListboxItem = forwardRef(
22
+ (
23
+ {
24
+ href,
25
+ label,
26
+ isChild = false,
27
+ onBlur,
28
+ nextItemRef,
29
+ prevItemRef,
30
+ tokens,
31
+ variant = {},
32
+ LinkRouter,
33
+ linkRouterProps,
34
+ id,
35
+ onPress = () => {},
36
+ ...rest
37
+ },
38
+ ref
39
+ ) => {
40
+ const selectedProps = selectProps({ href, ...rest })
41
+
42
+ const getTokens = useThemeTokensCallback('Listbox', tokens, variant, { isChild })
43
+ return (
44
+ <View style={[styles.itemContainer, isChild && styles.childContainer]} role="option">
45
+ <PressableItem
46
+ href={href}
47
+ isChild={isChild}
48
+ onPress={onPress}
49
+ onBlur={onBlur}
50
+ nextItemRef={nextItemRef}
51
+ prevItemRef={prevItemRef}
52
+ ref={ref}
53
+ tokens={getTokens}
54
+ selectedProps={selectedProps}
55
+ id={id}
56
+ >
57
+ {label}
58
+ </PressableItem>
59
+ </View>
60
+ )
61
+ }
62
+ )
63
+ ListboxItem.displayName = 'ListboxItem'
64
+
65
+ ListboxItem.propTypes = {
66
+ ...selectedSystemPropTypes,
67
+ ...withLinkRouter.propTypes,
68
+ href: PropTypes.string,
69
+ isChild: PropTypes.bool,
70
+ label: PropTypes.node.isRequired,
71
+ nextItemRef: PropTypes.object,
72
+ prevItemRef: PropTypes.object,
73
+ onPress: PropTypes.func
74
+ }
75
+
76
+ export default withLinkRouter(ListboxItem)
@@ -0,0 +1,82 @@
1
+ /* eslint-disable react/require-default-props */
2
+ import React, { forwardRef } from 'react'
3
+ import PropTypes from 'prop-types'
4
+ import { View, StyleSheet, Platform } from 'react-native'
5
+ import { Portal } from '@gorhom/portal'
6
+ import { useThemeTokens } from '../ThemeProvider'
7
+ import Card from '../Card'
8
+
9
+ const staticStyles = StyleSheet.create({
10
+ positioner: {
11
+ flex: 1, // Grow to maxWidth when possible, shrink when not possible
12
+ position: 'absolute',
13
+ zIndex: 1000000000000000 // Position on top of all the other overlays, including backdrops and modals
14
+ },
15
+ hidden: {
16
+ // Use opacity not visibility to hide the dropdown during positioning
17
+ // so on web, children may be focused from the first render
18
+ opacity: 0
19
+ }
20
+ })
21
+
22
+ const paddingVertical = 0
23
+ const paddingHorizontal = 0
24
+
25
+ const DropdownOverlay = forwardRef(
26
+ ({ children, isReady = false, overlaidPosition, maxWidth, minWidth, onLayout }, ref) => {
27
+ const systemTokens = useThemeTokens('Listbox', {}, {})
28
+
29
+ return (
30
+ <View
31
+ ref={ref}
32
+ onLayout={onLayout}
33
+ style={[
34
+ overlaidPosition,
35
+ { maxWidth, minWidth },
36
+ staticStyles.positioner,
37
+ !isReady && staticStyles.hidden
38
+ ]}
39
+ >
40
+ <Card
41
+ tokens={{
42
+ shadow: systemTokens.shadow,
43
+ paddingBottom: paddingVertical,
44
+ paddingTop: paddingVertical,
45
+ paddingLeft: paddingHorizontal,
46
+ paddingRight: paddingHorizontal
47
+ }}
48
+ >
49
+ {children}
50
+ </Card>
51
+ </View>
52
+ )
53
+ }
54
+ )
55
+
56
+ const withPortal = (Overlay) => {
57
+ // eslint-disable-next-line react/display-name, react/no-multi-comp
58
+ return (props) => {
59
+ return (
60
+ <Portal>
61
+ <Overlay {...props} />
62
+ </Portal>
63
+ )
64
+ }
65
+ }
66
+
67
+ DropdownOverlay.displayName = 'DropdownOverlay'
68
+
69
+ DropdownOverlay.propTypes = {
70
+ children: PropTypes.node.isRequired,
71
+ isReady: PropTypes.bool,
72
+ overlaidPosition: PropTypes.shape({
73
+ top: PropTypes.number,
74
+ left: PropTypes.number,
75
+ width: PropTypes.number
76
+ }),
77
+ maxWidth: PropTypes.number,
78
+ minWidth: PropTypes.number,
79
+ onLayout: PropTypes.func
80
+ }
81
+
82
+ export default Platform.OS === 'web' ? withPortal(DropdownOverlay) : DropdownOverlay
@@ -18,7 +18,6 @@ const getItemStyles = ({
18
18
  itemPaddingRight,
19
19
  itemBackgroundColor,
20
20
  itemColor,
21
- itemDisplay,
22
21
  itemOutline,
23
22
  itemTextDecoration,
24
23
  itemBorderLeftColor,
@@ -39,7 +38,6 @@ const getItemStyles = ({
39
38
  width: '100%',
40
39
  backgroundColor: itemBackgroundColor,
41
40
  color: itemColor,
42
- display: itemDisplay,
43
41
  outline: itemOutline,
44
42
  textDecoration: itemTextDecoration,
45
43
  borderLeft: `${itemBorderLeftWidth}px solid ${itemBorderLeftColor}`,
@@ -1,2 +1,3 @@
1
- export * from './ListboxContext'
2
- export { default as PressableItem } from './PressableItem'
1
+ import Listbox from './Listbox'
2
+
3
+ export default Listbox