@telus-uds/components-base 1.59.2 → 1.61.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 (90) hide show
  1. package/CHANGELOG.md +26 -2
  2. package/component-docs.json +526 -76
  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/Button/ButtonLink.js +7 -3
  10. package/lib/ExpandCollapse/Panel.js +7 -0
  11. package/lib/IconButton/IconButton.js +8 -0
  12. package/lib/Link/ChevronLink.js +9 -2
  13. package/lib/Link/LinkBase.js +14 -0
  14. package/lib/Link/TextButton.js +12 -1
  15. package/lib/Listbox/GroupControl.js +121 -0
  16. package/lib/Listbox/Listbox.js +198 -0
  17. package/lib/Listbox/ListboxGroup.js +142 -0
  18. package/lib/Listbox/ListboxItem.js +97 -0
  19. package/lib/Listbox/ListboxOverlay.js +106 -0
  20. package/lib/Listbox/PressableItem.js +0 -2
  21. package/lib/Listbox/index.js +5 -24
  22. package/lib/Pagination/dictionary.js +3 -3
  23. package/lib/Progress/ProgressBarBackground.js +2 -2
  24. package/lib/SideNav/Item.js +15 -5
  25. package/lib/Tags/Tags.js +6 -1
  26. package/lib/TextInput/TextInputBase.js +2 -0
  27. package/lib/Tooltip/Tooltip.js +6 -1
  28. package/lib/Tooltip/Tooltip.native.js +6 -1
  29. package/lib/Tooltip/shared.js +5 -0
  30. package/lib/index.js +17 -13
  31. package/lib/utils/useOverlaidPosition.js +6 -4
  32. package/lib-module/Autocomplete/Autocomplete.js +448 -0
  33. package/lib-module/Autocomplete/Loading.js +36 -0
  34. package/lib-module/Autocomplete/Suggestions.js +66 -0
  35. package/lib-module/Autocomplete/constants.js +4 -0
  36. package/lib-module/Autocomplete/dictionary.js +12 -0
  37. package/lib-module/Autocomplete/index.js +2 -0
  38. package/lib-module/Button/ButtonLink.js +4 -1
  39. package/lib-module/ExpandCollapse/Panel.js +7 -0
  40. package/lib-module/IconButton/IconButton.js +8 -0
  41. package/lib-module/Link/ChevronLink.js +10 -3
  42. package/lib-module/Link/LinkBase.js +14 -0
  43. package/lib-module/Link/TextButton.js +11 -1
  44. package/lib-module/Listbox/GroupControl.js +102 -0
  45. package/lib-module/Listbox/Listbox.js +172 -0
  46. package/lib-module/Listbox/ListboxGroup.js +117 -0
  47. package/lib-module/Listbox/ListboxItem.js +71 -0
  48. package/lib-module/Listbox/ListboxOverlay.js +80 -0
  49. package/lib-module/Listbox/PressableItem.js +0 -2
  50. package/lib-module/Listbox/index.js +2 -2
  51. package/lib-module/Pagination/dictionary.js +3 -3
  52. package/lib-module/Progress/ProgressBarBackground.js +2 -2
  53. package/lib-module/SideNav/Item.js +15 -5
  54. package/lib-module/Tags/Tags.js +6 -1
  55. package/lib-module/TextInput/TextInputBase.js +2 -0
  56. package/lib-module/Tooltip/Tooltip.js +6 -1
  57. package/lib-module/Tooltip/Tooltip.native.js +6 -1
  58. package/lib-module/Tooltip/shared.js +5 -0
  59. package/lib-module/index.js +2 -1
  60. package/lib-module/utils/useOverlaidPosition.js +5 -4
  61. package/package.json +5 -3
  62. package/src/Autocomplete/Autocomplete.jsx +411 -0
  63. package/src/Autocomplete/Loading.jsx +18 -0
  64. package/src/Autocomplete/Suggestions.jsx +54 -0
  65. package/src/Autocomplete/constants.js +4 -0
  66. package/src/Autocomplete/dictionary.js +12 -0
  67. package/src/Autocomplete/index.js +3 -0
  68. package/src/Button/ButtonLink.jsx +4 -1
  69. package/src/ExpandCollapse/Panel.jsx +11 -1
  70. package/src/IconButton/IconButton.jsx +7 -0
  71. package/src/Link/ChevronLink.jsx +10 -3
  72. package/src/Link/LinkBase.jsx +11 -0
  73. package/src/Link/TextButton.jsx +8 -2
  74. package/src/Listbox/GroupControl.jsx +93 -0
  75. package/src/Listbox/Listbox.jsx +165 -0
  76. package/src/Listbox/ListboxGroup.jsx +120 -0
  77. package/src/Listbox/ListboxItem.jsx +76 -0
  78. package/src/Listbox/ListboxOverlay.jsx +82 -0
  79. package/src/Listbox/PressableItem.jsx +0 -2
  80. package/src/Listbox/index.js +3 -2
  81. package/src/Pagination/dictionary.js +3 -3
  82. package/src/Progress/ProgressBarBackground.jsx +2 -2
  83. package/src/SideNav/Item.jsx +13 -5
  84. package/src/Tags/Tags.jsx +5 -1
  85. package/src/TextInput/TextInputBase.jsx +2 -0
  86. package/src/Tooltip/Tooltip.jsx +16 -2
  87. package/src/Tooltip/Tooltip.native.jsx +15 -2
  88. package/src/Tooltip/shared.js +4 -0
  89. package/src/index.js +2 -1
  90. package/src/utils/useOverlaidPosition.js +6 -5
@@ -107,6 +107,7 @@ const IconButton = forwardRef(
107
107
  icon: IconComponent,
108
108
  href,
109
109
  hrefAttrs,
110
+ testID,
110
111
  accessibilityRole = href ? 'link' : 'button',
111
112
  ...rawRest
112
113
  },
@@ -133,6 +134,7 @@ const IconButton = forwardRef(
133
134
  hrefAttrs={hrefAttrs}
134
135
  style={getOuterStyle}
135
136
  {...selectedProps}
137
+ testID={testID}
136
138
  >
137
139
  {(pressableState) => {
138
140
  const themeTokens = getTokens(resolvePressableState(pressableState))
@@ -157,6 +159,11 @@ IconButton.propTypes = {
157
159
  ...selectedSystemPropTypes,
158
160
  variant: variantProp.propType,
159
161
  tokens: getTokensPropType('IconButton'),
162
+ /**
163
+ * A unique identifier for testing purposes.
164
+ * Will be added as a `data-testid` attribute.
165
+ */
166
+ testID: PropTypes.string,
160
167
  /**
161
168
  * Defines the icon to be rendered
162
169
  */
@@ -2,7 +2,7 @@ import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
4
  import { useThemeTokensCallback } from '../ThemeProvider'
5
- import { selectTokens, getTokensPropType } from '../utils'
5
+ import { selectTokens, getTokensPropType, linkProps } from '../utils'
6
6
  import LinkBase from './LinkBase'
7
7
 
8
8
  /**
@@ -12,7 +12,7 @@ import LinkBase from './LinkBase'
12
12
  * ChevronLink is not intended to be deeply themable; variants passed to ChevronLink are forwarded to Link.
13
13
  */
14
14
  const ChevronLink = forwardRef(
15
- ({ direction = 'right', children, tokens = {}, variant, dataSet, ...linkProps }, ref) => {
15
+ ({ direction = 'right', children, tokens = {}, variant, dataSet, ...otherlinkProps }, ref) => {
16
16
  const getChevronTokens = useThemeTokensCallback('ChevronLink', tokens, variant)
17
17
  const applyChevronTokens = (linkState) => {
18
18
  const { leftIcon, rightIcon, iconDisplace, height, fontSize, ...otherTokens } =
@@ -32,7 +32,7 @@ const ChevronLink = forwardRef(
32
32
  const getTokens = useThemeTokensCallback('Link', applyChevronTokens, variant)
33
33
  return (
34
34
  <LinkBase
35
- {...linkProps}
35
+ {...otherlinkProps}
36
36
  iconPosition={direction}
37
37
  tokens={getTokens}
38
38
  dataSet={dataSet}
@@ -47,6 +47,13 @@ const ChevronLink = forwardRef(
47
47
  ChevronLink.displayName = 'ChevronLink'
48
48
  ChevronLink.propTypes = {
49
49
  ...LinkBase.propTypes,
50
+ children: PropTypes.node,
51
+ variant: PropTypes.exact({
52
+ size: PropTypes.oneOf(['large', 'small', 'micro']),
53
+ alternative: PropTypes.bool,
54
+ inverse: PropTypes.bool
55
+ }),
56
+ ...linkProps.types,
50
57
  tokens: getTokensPropType('ChevronLink', 'Link'),
51
58
  /**
52
59
  * Changes direction of chevron icon
@@ -197,6 +197,17 @@ LinkBase.displayName = 'LinkBase'
197
197
  LinkBase.propTypes = {
198
198
  ...selectedSystemPropTypes,
199
199
  tokens: getTokensPropType('Link'),
200
+ /**
201
+ * href for the Link
202
+ */
203
+ href: PropTypes.string,
204
+ /**
205
+ * AccesibilityRole for the link base
206
+ */
207
+ accessibilityrole: PropTypes.string,
208
+ /**
209
+ * Children nodes that can be added
210
+ */
200
211
  variant: variantProp.propType,
201
212
  /**
202
213
  * Optional variant that may be passed down to the link's icon if there is one
@@ -1,8 +1,8 @@
1
1
  import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
-
4
3
  import { useThemeTokensCallback } from '../ThemeProvider'
5
4
  import LinkBase from './LinkBase'
5
+ import { variantProp } from '../utils'
6
6
 
7
7
  /**
8
8
  * `TextButton` is a button that looks like a Link. It uses the same theming and variants as
@@ -29,7 +29,13 @@ TextButton.displayName = 'TextButton'
29
29
 
30
30
  TextButton.propTypes = {
31
31
  ...LinkBase.propTypes,
32
- onPress: PropTypes.func.isRequired
32
+ /** onPress function */
33
+ onPress: PropTypes.func.isRequired,
34
+ /** Children node that can be added */
35
+ children: PropTypes.node.isRequired,
36
+ variant: variantProp.propType,
37
+ /** Accesibility role for TextButton */
38
+ accessibilityRole: PropTypes.string
33
39
  }
34
40
 
35
41
  export default TextButton
@@ -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
@@ -8,11 +8,11 @@ export default {
8
8
  nextText: 'Next'
9
9
  },
10
10
  fr: {
11
- goToLabel: 'Aller au page n°',
11
+ goToLabel: 'Aller à la page n°',
12
12
  currentLabel: '(page actuelle)',
13
- previousLabel: 'Aller au page précédent',
13
+ previousLabel: 'Aller à la page précédente',
14
14
  previousText: 'Précédent',
15
- nextLabel: 'Aller au prochain page',
15
+ nextLabel: 'Aller à la page suivante',
16
16
  nextText: 'Suivant'
17
17
  }
18
18
  }