@telus-uds/components-base 3.22.0 → 3.24.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 (84) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/lib/cjs/Button/Button.js +2 -0
  3. package/lib/cjs/Button/ButtonBase.js +10 -5
  4. package/lib/cjs/Button/ButtonDropdown.js +2 -0
  5. package/lib/cjs/Button/ButtonGroup.js +45 -38
  6. package/lib/cjs/Button/propTypes.js +6 -0
  7. package/lib/cjs/Card/CardBase.js +97 -17
  8. package/lib/cjs/Card/PressableCardBase.js +12 -8
  9. package/lib/cjs/Carousel/Carousel.js +52 -19
  10. package/lib/cjs/Carousel/CarouselItem/CarouselItem.js +23 -3
  11. package/lib/cjs/HorizontalScroll/HorizontalScroll.js +5 -2
  12. package/lib/cjs/Icon/Icon.js +11 -11
  13. package/lib/cjs/Icon/IconText.js +0 -1
  14. package/lib/cjs/Listbox/GroupControl.js +44 -44
  15. package/lib/cjs/Listbox/Listbox.js +63 -20
  16. package/lib/cjs/Listbox/ListboxGroup.js +141 -9
  17. package/lib/cjs/Listbox/ListboxOverlay.js +13 -5
  18. package/lib/cjs/Listbox/PressableItem.js +8 -4
  19. package/lib/cjs/Listbox/SecondLevelHeader.js +201 -0
  20. package/lib/cjs/Listbox/dictionary.js +14 -0
  21. package/lib/cjs/Shortcuts/Shortcuts.js +169 -0
  22. package/lib/cjs/Shortcuts/ShortcutsItem.js +280 -0
  23. package/lib/cjs/Shortcuts/index.js +16 -0
  24. package/lib/cjs/TextInput/TextInputBase.js +5 -1
  25. package/lib/cjs/Tooltip/Tooltip.native.js +2 -0
  26. package/lib/cjs/Validator/Validator.js +171 -135
  27. package/lib/cjs/index.js +15 -0
  28. package/lib/esm/Button/Button.js +2 -0
  29. package/lib/esm/Button/ButtonBase.js +10 -5
  30. package/lib/esm/Button/ButtonDropdown.js +2 -0
  31. package/lib/esm/Button/ButtonGroup.js +44 -39
  32. package/lib/esm/Button/propTypes.js +6 -0
  33. package/lib/esm/Card/CardBase.js +97 -17
  34. package/lib/esm/Card/PressableCardBase.js +10 -8
  35. package/lib/esm/Carousel/Carousel.js +52 -19
  36. package/lib/esm/Carousel/CarouselItem/CarouselItem.js +23 -3
  37. package/lib/esm/HorizontalScroll/HorizontalScroll.js +6 -3
  38. package/lib/esm/Icon/Icon.js +11 -11
  39. package/lib/esm/Icon/IconText.js +0 -1
  40. package/lib/esm/Listbox/GroupControl.js +44 -44
  41. package/lib/esm/Listbox/Listbox.js +64 -21
  42. package/lib/esm/Listbox/ListboxGroup.js +143 -11
  43. package/lib/esm/Listbox/ListboxOverlay.js +13 -5
  44. package/lib/esm/Listbox/PressableItem.js +8 -4
  45. package/lib/esm/Listbox/SecondLevelHeader.js +194 -0
  46. package/lib/esm/Listbox/dictionary.js +8 -0
  47. package/lib/esm/Shortcuts/Shortcuts.js +160 -0
  48. package/lib/esm/Shortcuts/ShortcutsItem.js +273 -0
  49. package/lib/esm/Shortcuts/index.js +3 -0
  50. package/lib/esm/TextInput/TextInputBase.js +5 -1
  51. package/lib/esm/Tooltip/Tooltip.native.js +2 -0
  52. package/lib/esm/Validator/Validator.js +171 -135
  53. package/lib/esm/index.js +1 -0
  54. package/lib/package.json +2 -2
  55. package/package.json +2 -2
  56. package/src/Button/Button.jsx +2 -1
  57. package/src/Button/ButtonBase.jsx +18 -12
  58. package/src/Button/ButtonDropdown.jsx +2 -0
  59. package/src/Button/ButtonGroup.jsx +62 -45
  60. package/src/Button/propTypes.js +6 -0
  61. package/src/Card/CardBase.jsx +113 -14
  62. package/src/Card/PressableCardBase.jsx +17 -5
  63. package/src/Carousel/Carousel.jsx +58 -5
  64. package/src/Carousel/CarouselItem/CarouselItem.jsx +31 -3
  65. package/src/HorizontalScroll/HorizontalScroll.jsx +6 -3
  66. package/src/Icon/Icon.jsx +14 -14
  67. package/src/Icon/IconText.jsx +0 -1
  68. package/src/Listbox/GroupControl.jsx +72 -70
  69. package/src/Listbox/Listbox.jsx +67 -11
  70. package/src/Listbox/ListboxGroup.jsx +160 -27
  71. package/src/Listbox/ListboxOverlay.jsx +23 -5
  72. package/src/Listbox/PressableItem.jsx +8 -4
  73. package/src/Listbox/SecondLevelHeader.jsx +182 -0
  74. package/src/Listbox/dictionary.js +8 -0
  75. package/src/Shortcuts/Shortcuts.jsx +174 -0
  76. package/src/Shortcuts/ShortcutsItem.jsx +297 -0
  77. package/src/Shortcuts/index.js +4 -0
  78. package/src/TextInput/TextInputBase.jsx +5 -1
  79. package/src/Tooltip/Tooltip.native.jsx +2 -1
  80. package/src/Validator/Validator.jsx +180 -159
  81. package/src/index.js +1 -0
  82. package/types/Listbox.d.ts +24 -0
  83. package/types/Shortcuts.d.ts +136 -0
  84. package/types/index.d.ts +12 -0
@@ -1,13 +1,14 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { View, StyleSheet, Text } from 'react-native'
3
+ import { View, Text, StyleSheet } from 'react-native'
4
4
  import { useThemeTokens } from '../ThemeProvider'
5
5
  import Icon from '../Icon'
6
6
  import Spacer from '../Spacer'
7
7
  import { useListboxContext } from './ListboxContext'
8
+ import { variantProp } from '../utils'
8
9
 
9
10
  const styles = StyleSheet.create({
10
- controlWrapper: {
11
+ container: {
11
12
  width: '100%',
12
13
  flex: 1,
13
14
  alignItems: 'center',
@@ -17,76 +18,76 @@ const styles = StyleSheet.create({
17
18
  }
18
19
  })
19
20
 
20
- const GroupControl = React.forwardRef(({ expanded, pressed, hover, focus, label, id }, ref) => {
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
-
52
- const getTextStyles = () => ({
53
- color: groupColor
54
- })
21
+ const selectTextStyles = (tokens) => ({
22
+ color: tokens.groupColor,
23
+ fontFamily: `${tokens.groupFontName}${tokens.groupFontWeight}normal`,
24
+ fontSize: tokens.groupFontSize,
25
+ fontWeight: tokens.groupFontWeight
26
+ })
55
27
 
56
- return (
57
- <View
58
- onPress={() => setSelectedId(id)}
59
- style={[
60
- styles.controlWrapper,
61
- {
62
- fontFamily: `${groupFontName}${groupFontWeight}normal`,
63
- fontSize: groupFontSize,
64
- color: groupColor,
65
- textDecoration: itemTextDecoration,
66
- backgroundColor: groupBackgroundColor,
67
- outline: itemOutline,
68
- height: groupHeight,
69
- border: `${groupBorderWidth}px solid ${groupBorderColor}`,
70
- borderRadius: groupBorderRadius,
71
- paddingLeft: groupPaddingLeft - groupBorderWidth,
72
- paddingRight: groupPaddingRight - groupBorderWidth,
73
- paddingTop: groupPaddingTop - groupBorderWidth,
74
- paddingBottom: groupPaddingBottom - groupBorderWidth
75
- }
76
- ]}
77
- ref={ref}
78
- >
79
- <Text style={getTextStyles()}>{label}</Text>
80
- <Spacer space={1} direction="row" />
81
- <Icon
82
- icon={tokens.groupIcon}
83
- tokens={{ color: tokens.groupColor }}
84
- variant={{ size: 'micro' }}
85
- />
86
- </View>
87
- )
28
+ const selectContainerStyles = (tokens) => ({
29
+ fontFamily: `${tokens.groupFontName}${tokens.groupFontWeight}normal`,
30
+ fontSize: tokens.groupFontSize,
31
+ color: tokens.groupColor,
32
+ textDecoration: tokens.itemTextDecoration,
33
+ backgroundColor: tokens.groupBackgroundColor,
34
+ outline: tokens.itemOutline,
35
+ minHeight: tokens.groupHeight,
36
+ borderRadius: tokens.groupBorderRadius,
37
+ paddingLeft: tokens.groupPaddingLeft - tokens.groupBorderLeftWidth,
38
+ paddingRight: tokens.groupPaddingRight - tokens.groupBorderRightWidth,
39
+ paddingTop: tokens.groupPaddingTop - tokens.groupBorderTopWidth,
40
+ paddingBottom: tokens.groupPaddingBottom - tokens.groupBorderBottomWidth,
41
+ borderLeftWidth: tokens.groupBorderLeftWidth,
42
+ borderLeftColor: tokens.groupBorderLeftColor,
43
+ borderRightWidth: tokens.groupBorderRightWidth,
44
+ borderRightColor: tokens.groupBorderRightColor,
45
+ borderTopWidth: tokens.groupBorderTopWidth,
46
+ borderTopColor: tokens.groupBorderTopColor,
47
+ borderBottomWidth: tokens.groupBorderBottomWidth,
48
+ borderBottomColor: tokens.groupBorderBottomColor
88
49
  })
89
50
 
51
+ const GroupControl = React.forwardRef(
52
+ ({ expanded, pressed, hover, focus, label, id, variant = {} }, ref) => {
53
+ const { selectedId, setSelectedId } = useListboxContext()
54
+ const isSecondLevel = variant?.secondLevel === true
55
+ const tokens = useThemeTokens(
56
+ 'Listbox',
57
+ variant,
58
+ {},
59
+ {
60
+ expanded,
61
+ pressed,
62
+ hover,
63
+ current: selectedId === id && id !== undefined,
64
+ focus,
65
+ secondLevel: isSecondLevel
66
+ }
67
+ )
68
+
69
+ const displayIcon = isSecondLevel ? tokens.secondLevelParentIcon : tokens.groupIcon
70
+
71
+ return (
72
+ <View
73
+ onPress={() => setSelectedId(id)}
74
+ style={[styles.container, selectContainerStyles(tokens)]}
75
+ ref={ref}
76
+ >
77
+ <Text style={selectTextStyles(tokens)}>{label}</Text>
78
+ <Spacer space={1} direction="row" />
79
+ {displayIcon && (
80
+ <Icon
81
+ icon={displayIcon}
82
+ tokens={{ color: tokens.groupColor }}
83
+ variant={{ size: 'micro' }}
84
+ />
85
+ )}
86
+ </View>
87
+ )
88
+ }
89
+ )
90
+
90
91
  GroupControl.displayName = 'GroupControl'
91
92
 
92
93
  GroupControl.propTypes = {
@@ -95,7 +96,8 @@ GroupControl.propTypes = {
95
96
  pressed: PropTypes.bool,
96
97
  hover: PropTypes.bool,
97
98
  focus: PropTypes.bool,
98
- label: PropTypes.string
99
+ label: PropTypes.string,
100
+ variant: variantProp.propType
99
101
  }
100
102
 
101
103
  export default GroupControl
@@ -1,21 +1,30 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { View, StyleSheet, Platform } from 'react-native'
3
+ import { View, Platform, StyleSheet } from 'react-native'
4
4
  import { useThemeTokens } from '../ThemeProvider'
5
- import { withLinkRouter, getTokensPropType } from '../utils'
5
+ import { withLinkRouter, getTokensPropType, variantProp } from '../utils'
6
6
  import ExpandCollapse from '../ExpandCollapse'
7
7
  import ListboxGroup from './ListboxGroup'
8
8
  import ListboxItem from './ListboxItem'
9
9
  import { ListboxContext } from './ListboxContext'
10
10
  import DropdownOverlay from './ListboxOverlay'
11
+ import defaultDictionary from './dictionary'
11
12
 
12
13
  const styles = StyleSheet.create({
13
- list: {
14
+ container: {
14
15
  padding: 0,
15
- margin: 0
16
+ margin: 0,
17
+ position: 'relative',
18
+ overflow: 'visible'
16
19
  }
17
20
  })
18
21
 
22
+ const selectContainerStyles = (tokens) => ({
23
+ minHeight: tokens.minHeight,
24
+ minWidth: tokens.minWidth,
25
+ backgroundColor: tokens.containerBackgroundColor
26
+ })
27
+
19
28
  const getInitialOpen = (items, selectedId) =>
20
29
  items
21
30
  .filter(
@@ -35,16 +44,18 @@ const Listbox = React.forwardRef(
35
44
  LinkRouter,
36
45
  itemRouterProps,
37
46
  onClose,
47
+ copy = 'en',
48
+ dictionary = defaultDictionary,
38
49
  variant,
39
- tokens
50
+ tokens,
51
+ testID
40
52
  },
41
53
  ref
42
54
  ) => {
43
55
  const initialOpen = getInitialOpen(items, defaultSelectedId)
44
-
45
56
  const [selectedId, setSelectedId] = React.useState(defaultSelectedId)
46
-
47
- const { minHeight, minWidth } = useThemeTokens('Listbox', variant, tokens)
57
+ const [activeSecondLevelGroup, setActiveSecondLevelGroup] = React.useState(null)
58
+ const listboxTokens = useThemeTokens('Listbox', tokens, variant)
48
59
 
49
60
  // We need to keep track of each item's ref in order to be able to
50
61
  // focus on a specific item via keyboard navigation
@@ -97,11 +108,22 @@ const Listbox = React.forwardRef(
97
108
  return () => {}
98
109
  }, [onClose, handleKeydown])
99
110
 
111
+ const contextValue = {
112
+ selectedId,
113
+ setSelectedId,
114
+ activeSecondLevelGroup,
115
+ setActiveSecondLevelGroup
116
+ }
117
+
100
118
  return (
101
- <ListboxContext.Provider value={{ selectedId, setSelectedId }}>
119
+ <ListboxContext.Provider value={contextValue}>
102
120
  <ExpandCollapse initialOpen={initialOpen} maxOpen={1} ref={ref}>
103
121
  {(expandProps) => (
104
- <View style={[styles.list, { minHeight, minWidth }]} role="listbox">
122
+ <View
123
+ style={[styles.container, selectContainerStyles(listboxTokens)]}
124
+ accessibilityRole="combobox"
125
+ testID={testID}
126
+ >
105
127
  {items.map((item, index) => {
106
128
  const { id, label, items: nestedItems } = item
107
129
  const itemId = id ?? label
@@ -112,6 +134,10 @@ const Listbox = React.forwardRef(
112
134
  return currentItemRef
113
135
  }
114
136
 
137
+ if (!nestedItems && activeSecondLevelGroup) {
138
+ return null
139
+ }
140
+
115
141
  return nestedItems ? (
116
142
  <ListboxGroup
117
143
  {...item}
@@ -122,6 +148,11 @@ const Listbox = React.forwardRef(
122
148
  nextItemRef={itemRefs.current[index + 1] ?? null}
123
149
  ref={index === 0 ? firstItemRef : itemRef}
124
150
  key={itemId}
151
+ copy={copy}
152
+ dictionary={dictionary}
153
+ variant={variant}
154
+ tokens={tokens}
155
+ onClose={onClose}
125
156
  />
126
157
  ) : (
127
158
  <ListboxItem
@@ -133,6 +164,8 @@ const Listbox = React.forwardRef(
133
164
  prevItemRef={itemRefs.current[index - 1] ?? null}
134
165
  nextItemRef={itemRefs.current[index + 1] ?? null}
135
166
  ref={index === 0 ? firstItemRef : itemRef}
167
+ variant={variant}
168
+ tokens={tokens}
136
169
  />
137
170
  )
138
171
  })}
@@ -169,7 +202,30 @@ Listbox.propTypes = {
169
202
  /**
170
203
  * onClose event
171
204
  */
172
- onClose: PropTypes.func
205
+ onClose: PropTypes.func,
206
+ /**
207
+ * Test ID for testing
208
+ */
209
+ testID: PropTypes.string,
210
+ /**
211
+ * Select English or French copy
212
+ */
213
+ copy: PropTypes.oneOf(['en', 'fr']),
214
+ /**
215
+ * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
216
+ */
217
+ dictionary: PropTypes.shape({
218
+ en: PropTypes.shape({
219
+ closeMenu: PropTypes.string.isRequired
220
+ }),
221
+ fr: PropTypes.shape({
222
+ closeMenu: PropTypes.string.isRequired
223
+ })
224
+ }),
225
+ /**
226
+ * Listbox variant
227
+ */
228
+ variant: variantProp.propType
173
229
  }
174
230
 
175
231
  Listbox.Overlay = DropdownOverlay
@@ -2,21 +2,36 @@
2
2
  import React from 'react'
3
3
  import PropTypes from 'prop-types'
4
4
  import { View, StyleSheet, Platform } from 'react-native'
5
- import { withLinkRouter } from '../utils'
5
+ import { withLinkRouter, variantProp, copyPropTypes } from '../utils'
6
+ import { useThemeTokens } from '../ThemeProvider'
6
7
  import ExpandCollapse from '../ExpandCollapse'
7
8
  import ListboxItem from './ListboxItem'
8
9
  import { useListboxContext } from './ListboxContext'
9
10
  import GroupControl from './GroupControl'
11
+ import SecondLevelHeader from './SecondLevelHeader'
12
+ import defaultDictionary from './dictionary'
10
13
 
11
14
  const styles = StyleSheet.create({
12
15
  groupWrapper: {
13
16
  margin: 0,
14
17
  padding: 0,
15
- overflow: 'hidden'
18
+ overflow: 'visible'
16
19
  },
17
20
  list: {
18
21
  margin: 0,
19
22
  padding: 0
23
+ },
24
+ secondLevelContainer: {
25
+ margin: 0,
26
+ padding: 0,
27
+ width: '100%',
28
+ display: 'flex',
29
+ flexDirection: 'column'
30
+ },
31
+ secondLevelList: {
32
+ margin: 0,
33
+ padding: 0,
34
+ width: '100%'
20
35
  }
21
36
  })
22
37
 
@@ -27,6 +42,10 @@ const getAccessibilityRole = () =>
27
42
  web: 'listitem'
28
43
  })
29
44
 
45
+ const selectSecondLevelContainerStyles = ({ secondLevelHeaderBackgroundColor }) => ({
46
+ backgroundColor: secondLevelHeaderBackgroundColor
47
+ })
48
+
30
49
  const ListboxGroup = React.forwardRef(
31
50
  (
32
51
  {
@@ -38,11 +57,86 @@ const ListboxGroup = React.forwardRef(
38
57
  expandProps,
39
58
  onLastItemBlur,
40
59
  nextItemRef,
41
- prevItemRef
60
+ prevItemRef,
61
+ copy = 'en',
62
+ dictionary = defaultDictionary,
63
+ variant = {},
64
+ tokens = {},
65
+ onClose
42
66
  },
43
67
  ref
44
68
  ) => {
45
- const { selectedId } = useListboxContext()
69
+ const { selectedId, activeSecondLevelGroup, setActiveSecondLevelGroup } = useListboxContext()
70
+ const [secondLevelOpen, setSecondLevelOpen] = React.useState(false)
71
+ const isSecondLevel = variant?.secondLevel === true
72
+ const listboxTokens = useThemeTokens('Listbox', variant, tokens)
73
+ const groupId = id ?? label
74
+
75
+ const handleGroupClick = React.useCallback(() => {
76
+ if (isSecondLevel) {
77
+ setSecondLevelOpen(true)
78
+ setActiveSecondLevelGroup(groupId)
79
+ }
80
+ }, [isSecondLevel, groupId, setActiveSecondLevelGroup])
81
+
82
+ const handleBackClick = React.useCallback(() => {
83
+ setSecondLevelOpen(false)
84
+ setActiveSecondLevelGroup(null)
85
+ }, [setActiveSecondLevelGroup])
86
+
87
+ const handleCloseClick = React.useCallback(() => {
88
+ setSecondLevelOpen(false)
89
+ setActiveSecondLevelGroup(null)
90
+ if (onClose) {
91
+ onClose()
92
+ }
93
+ }, [setActiveSecondLevelGroup, onClose])
94
+
95
+ if (isSecondLevel && activeSecondLevelGroup && activeSecondLevelGroup !== groupId) {
96
+ return null
97
+ }
98
+
99
+ if (isSecondLevel && secondLevelOpen) {
100
+ return (
101
+ <View
102
+ style={[styles.secondLevelContainer, selectSecondLevelContainerStyles(listboxTokens)]}
103
+ >
104
+ <SecondLevelHeader
105
+ label={label}
106
+ onBack={handleBackClick}
107
+ onClose={handleCloseClick}
108
+ copy={copy}
109
+ dictionary={dictionary}
110
+ variant={variant}
111
+ tokens={tokens}
112
+ />
113
+ <View style={styles.secondLevelList}>
114
+ {items &&
115
+ items.map((item, index) => {
116
+ return (
117
+ <ListboxItem
118
+ key={item.label}
119
+ id={item.id ?? item.label}
120
+ {...item}
121
+ selected={
122
+ (item.id && item.id === selectedId) ||
123
+ (item.label && item.label === selectedId)
124
+ }
125
+ isChild={false}
126
+ LinkRouter={LinkRouter}
127
+ linkRouterProps={linkRouterProps}
128
+ variant={variant}
129
+ tokens={tokens}
130
+ {...(index === 0 && { prevItemRef })}
131
+ {...(index === items.length - 1 && { nextItemRef })}
132
+ {...(index === items.length - 1 && { onBlur: onLastItemBlur })}
133
+ />
134
+ )
135
+ })}
136
+ </View>
137
+ </View>
138
+ )
139
+ }
46
140
 
47
141
  // TODO: implement keyboard navigation via refs for grouped items separately here
48
142
  return (
@@ -63,9 +157,11 @@ const ListboxGroup = React.forwardRef(
63
157
  // TODO refactor
64
158
  // eslint-disable-next-line react/no-unstable-nested-components
65
159
  control={(controlProps) => (
66
- <GroupControl id={id ?? label} {...controlProps} label={label} />
160
+ <GroupControl id={id ?? label} {...controlProps} label={label} variant={variant} />
67
161
  )}
68
162
  {...expandProps}
163
+ {...(isSecondLevel && { open: false })}
164
+ {...(isSecondLevel && { onPress: handleGroupClick })}
69
165
  tokens={{
70
166
  contentPaddingLeft: 0,
71
167
  contentPaddingRight: 0,
@@ -74,30 +170,36 @@ const ListboxGroup = React.forwardRef(
74
170
  borderColor: 'transparent',
75
171
  borderRadius: 0,
76
172
  borderWidth: 0,
77
- marginBottom: 0
173
+ marginBottom: 0,
174
+ contentPanelBackgroundColor: 'transparent'
78
175
  }}
79
176
  controlRef={ref}
80
177
  >
81
- <View style={styles.list}>
82
- {items.map((item, index) => {
83
- return (
84
- <ListboxItem
85
- key={item.label}
86
- id={item.id ?? item.label}
87
- {...item}
88
- selected={
89
- (item.id && item.id === selectedId) || (item.label && item.label === selectedId)
90
- }
91
- isChild
92
- LinkRouter={LinkRouter}
93
- linkRouterProps={linkRouterProps}
94
- {...(index === 0 && { prevItemRef })}
95
- {...(index === items.length - 1 && { nextItemRef })}
96
- {...(index === items.length - 1 && { onBlur: onLastItemBlur })}
97
- />
98
- )
99
- })}
100
- </View>
178
+ {!isSecondLevel && (
179
+ <View style={styles.list}>
180
+ {items.map((item, index) => {
181
+ return (
182
+ <ListboxItem
183
+ key={item.label}
184
+ id={item.id ?? item.label}
185
+ {...item}
186
+ selected={
187
+ (item.id && item.id === selectedId) ||
188
+ (item.label && item.label === selectedId)
189
+ }
190
+ isChild
191
+ LinkRouter={LinkRouter}
192
+ linkRouterProps={linkRouterProps}
193
+ variant={variant}
194
+ tokens={tokens}
195
+ {...(index === 0 && { prevItemRef })}
196
+ {...(index === items.length - 1 && { nextItemRef })}
197
+ {...(index === items.length - 1 && { onBlur: onLastItemBlur })}
198
+ />
199
+ )
200
+ })}
201
+ </View>
202
+ )}
101
203
  </ExpandCollapse.Panel>
102
204
  </View>
103
205
  )
@@ -107,6 +209,10 @@ ListboxGroup.displayName = 'ListboxGroup'
107
209
 
108
210
  ListboxGroup.propTypes = {
109
211
  ...withLinkRouter.propTypes,
212
+ /**
213
+ * Unique identifier for the group
214
+ */
215
+ id: PropTypes.string,
110
216
  label: PropTypes.string,
111
217
  items: PropTypes.arrayOf(
112
218
  PropTypes.shape({
@@ -121,7 +227,34 @@ ListboxGroup.propTypes = {
121
227
  /**
122
228
  * Use this callback to redirect the focus after it leaves the last item of the group.
123
229
  */
124
- onLastItemBlur: PropTypes.func
230
+ onLastItemBlur: PropTypes.func,
231
+ /**
232
+ * Select English or French copy
233
+ */
234
+ copy: copyPropTypes,
235
+ /**
236
+ * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
237
+ */
238
+ dictionary: PropTypes.shape({
239
+ en: PropTypes.shape({
240
+ closeMenu: PropTypes.string.isRequired
241
+ }),
242
+ fr: PropTypes.shape({
243
+ closeMenu: PropTypes.string.isRequired
244
+ })
245
+ }),
246
+ /**
247
+ * Variant configuration for secondLevel behavior
248
+ */
249
+ variant: variantProp.propType,
250
+ /**
251
+ * Custom tokens
252
+ */
253
+ tokens: PropTypes.object,
254
+ /**
255
+ * Callback when the menu is closed
256
+ */
257
+ onClose: PropTypes.func
125
258
  }
126
259
 
127
260
  export default ListboxGroup
@@ -22,13 +22,28 @@ const paddingVertical = 0
22
22
  const paddingHorizontal = 0
23
23
 
24
24
  const DropdownOverlay = React.forwardRef(
25
- ({ children, isReady = false, overlaidPosition, maxWidth, minWidth, onLayout, tokens }, ref) => {
26
- const systemTokens = useThemeTokens('Listbox', {}, {})
25
+ (
26
+ {
27
+ children,
28
+ isReady = false,
29
+ overlaidPosition,
30
+ maxWidth,
31
+ minWidth,
32
+ onLayout,
33
+ tokens,
34
+ testID,
35
+ variant
36
+ },
37
+
38
+ ref
39
+ ) => {
40
+ const systemTokens = useThemeTokens('Listbox', variant, tokens)
27
41
 
28
42
  return (
29
43
  <View
30
44
  ref={ref}
31
45
  onLayout={onLayout}
46
+ testID={testID}
32
47
  style={[
33
48
  overlaidPosition,
34
49
  { maxWidth, minWidth },
@@ -39,11 +54,12 @@ const DropdownOverlay = React.forwardRef(
39
54
  <Card
40
55
  tokens={{
41
56
  shadow: systemTokens.shadow,
57
+ borderRadius: systemTokens.borderRadius,
58
+ ...(Platform.OS === 'web' && { overflowY: 'hidden' }),
42
59
  paddingBottom: paddingVertical,
43
60
  paddingTop: paddingVertical,
44
61
  paddingLeft: paddingHorizontal,
45
- paddingRight: paddingHorizontal,
46
- ...tokens
62
+ paddingRight: paddingHorizontal
47
63
  }}
48
64
  >
49
65
  {children}
@@ -75,7 +91,9 @@ DropdownOverlay.propTypes = {
75
91
  maxWidth: PropTypes.number,
76
92
  minWidth: PropTypes.number,
77
93
  onLayout: PropTypes.func,
78
- tokens: PropTypes.object
94
+ tokens: PropTypes.object,
95
+ testID: PropTypes.string,
96
+ variant: PropTypes.object
79
97
  }
80
98
 
81
99
  export default Platform.OS === 'web' ? withPortal(DropdownOverlay) : DropdownOverlay
@@ -39,10 +39,14 @@ const getItemStyles = ({
39
39
  color: itemColor,
40
40
  outline: itemOutline,
41
41
  textDecoration: itemTextDecoration,
42
- borderLeft: `${itemBorderLeftWidth}px solid ${itemBorderLeftColor}`,
43
- borderRight: `${itemBorderRightWidth}px solid ${itemBorderRightColor}`,
44
- borderTop: `${itemBorderTopWidth}px solid ${itemBorderTopColor}`,
45
- borderBottom: `${itemBorderBottomWidth}px solid ${itemBorderBottomColor}`,
42
+ borderLeftWidth: itemBorderLeftWidth,
43
+ borderLeftColor: itemBorderLeftColor,
44
+ borderRightWidth: itemBorderRightWidth,
45
+ borderRightColor: itemBorderRightColor,
46
+ borderTopWidth: itemBorderTopWidth,
47
+ borderTopColor: itemBorderTopColor,
48
+ borderBottomWidth: itemBorderBottomWidth,
49
+ borderBottomColor: itemBorderBottomColor,
46
50
  borderRadius: itemBorderRadius,
47
51
  justifyContent: 'center'
48
52
  })