@telus-uds/components-base 3.23.0 → 3.25.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 (73) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/lib/cjs/Button/ButtonGroup.js +9 -2
  3. package/lib/cjs/Card/CardBase.js +97 -17
  4. package/lib/cjs/Card/PressableCardBase.js +12 -8
  5. package/lib/cjs/Carousel/Carousel.js +35 -4
  6. package/lib/cjs/FlexGrid/FlexGrid.js +31 -35
  7. package/lib/cjs/HorizontalScroll/HorizontalScroll.js +5 -2
  8. package/lib/cjs/Icon/Icon.js +3 -0
  9. package/lib/cjs/IconButton/IconButton.js +15 -5
  10. package/lib/cjs/Listbox/GroupControl.js +12 -6
  11. package/lib/cjs/Listbox/Listbox.js +41 -7
  12. package/lib/cjs/Listbox/ListboxGroup.js +139 -8
  13. package/lib/cjs/Listbox/ListboxOverlay.js +10 -5
  14. package/lib/cjs/Listbox/SecondLevelHeader.js +201 -0
  15. package/lib/cjs/Listbox/dictionary.js +14 -0
  16. package/lib/cjs/Shortcuts/Shortcuts.js +169 -0
  17. package/lib/cjs/Shortcuts/ShortcutsItem.js +280 -0
  18. package/lib/cjs/Shortcuts/index.js +16 -0
  19. package/lib/cjs/TextInput/TextInputBase.js +2 -3
  20. package/lib/cjs/Tooltip/Tooltip.native.js +2 -0
  21. package/lib/cjs/index.js +15 -0
  22. package/lib/cjs/utils/index.js +9 -1
  23. package/lib/cjs/utils/resolveContentMaxWidth.js +30 -0
  24. package/lib/esm/Button/ButtonGroup.js +9 -2
  25. package/lib/esm/Card/CardBase.js +97 -17
  26. package/lib/esm/Card/PressableCardBase.js +10 -8
  27. package/lib/esm/Carousel/Carousel.js +37 -6
  28. package/lib/esm/FlexGrid/FlexGrid.js +31 -35
  29. package/lib/esm/HorizontalScroll/HorizontalScroll.js +6 -3
  30. package/lib/esm/Icon/Icon.js +3 -0
  31. package/lib/esm/IconButton/IconButton.js +15 -5
  32. package/lib/esm/Listbox/GroupControl.js +12 -6
  33. package/lib/esm/Listbox/Listbox.js +41 -7
  34. package/lib/esm/Listbox/ListboxGroup.js +141 -10
  35. package/lib/esm/Listbox/ListboxOverlay.js +10 -5
  36. package/lib/esm/Listbox/SecondLevelHeader.js +194 -0
  37. package/lib/esm/Listbox/dictionary.js +8 -0
  38. package/lib/esm/Shortcuts/Shortcuts.js +160 -0
  39. package/lib/esm/Shortcuts/ShortcutsItem.js +273 -0
  40. package/lib/esm/Shortcuts/index.js +3 -0
  41. package/lib/esm/TextInput/TextInputBase.js +2 -3
  42. package/lib/esm/Tooltip/Tooltip.native.js +2 -0
  43. package/lib/esm/index.js +1 -0
  44. package/lib/esm/utils/index.js +2 -1
  45. package/lib/esm/utils/resolveContentMaxWidth.js +24 -0
  46. package/lib/package.json +2 -2
  47. package/package.json +2 -2
  48. package/src/Button/ButtonGroup.jsx +20 -3
  49. package/src/Card/CardBase.jsx +113 -14
  50. package/src/Card/PressableCardBase.jsx +17 -5
  51. package/src/Carousel/Carousel.jsx +38 -6
  52. package/src/FlexGrid/FlexGrid.jsx +30 -39
  53. package/src/HorizontalScroll/HorizontalScroll.jsx +6 -3
  54. package/src/Icon/Icon.jsx +3 -0
  55. package/src/IconButton/IconButton.jsx +12 -5
  56. package/src/Listbox/GroupControl.jsx +41 -33
  57. package/src/Listbox/Listbox.jsx +41 -2
  58. package/src/Listbox/ListboxGroup.jsx +158 -26
  59. package/src/Listbox/ListboxOverlay.jsx +18 -5
  60. package/src/Listbox/SecondLevelHeader.jsx +182 -0
  61. package/src/Listbox/dictionary.js +8 -0
  62. package/src/Shortcuts/Shortcuts.jsx +174 -0
  63. package/src/Shortcuts/ShortcutsItem.jsx +297 -0
  64. package/src/Shortcuts/index.js +4 -0
  65. package/src/TextInput/TextInputBase.jsx +2 -2
  66. package/src/Tooltip/Tooltip.native.jsx +2 -1
  67. package/src/index.js +1 -0
  68. package/src/utils/index.js +1 -0
  69. package/src/utils/resolveContentMaxWidth.js +28 -0
  70. package/types/Listbox.d.ts +24 -0
  71. package/types/Shortcuts.d.ts +136 -0
  72. package/types/Status.d.ts +42 -0
  73. package/types/index.d.ts +15 -0
@@ -5,6 +5,7 @@ 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
11
  container: {
@@ -47,39 +48,45 @@ const selectContainerStyles = (tokens) => ({
47
48
  borderBottomColor: tokens.groupBorderBottomColor
48
49
  })
49
50
 
50
- const GroupControl = React.forwardRef(({ expanded, pressed, hover, focus, label, id }, ref) => {
51
- const { selectedId, setSelectedId } = useListboxContext()
52
- const tokens = useThemeTokens(
53
- 'Listbox',
54
- {},
55
- {},
56
- {
57
- expanded,
58
- pressed,
59
- hover,
60
- current: selectedId === id && id !== undefined,
61
- focus
62
- }
63
- )
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
+ )
64
68
 
65
- return (
66
- <View
67
- onPress={() => setSelectedId(id)}
68
- style={[styles.container, selectContainerStyles(tokens)]}
69
- ref={ref}
70
- >
71
- <Text style={selectTextStyles(tokens)}>{label}</Text>
72
- <Spacer space={1} direction="row" />
73
- {tokens.groupIcon && (
74
- <Icon
75
- icon={tokens.groupIcon}
76
- tokens={{ color: tokens.groupColor }}
77
- variant={{ size: 'micro' }}
78
- />
79
- )}
80
- </View>
81
- )
82
- })
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
+ )
83
90
 
84
91
  GroupControl.displayName = 'GroupControl'
85
92
 
@@ -89,7 +96,8 @@ GroupControl.propTypes = {
89
96
  pressed: PropTypes.bool,
90
97
  hover: PropTypes.bool,
91
98
  focus: PropTypes.bool,
92
- label: PropTypes.string
99
+ label: PropTypes.string,
100
+ variant: variantProp.propType
93
101
  }
94
102
 
95
103
  export default GroupControl
@@ -8,11 +8,14 @@ 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
14
  container: {
14
15
  padding: 0,
15
- margin: 0
16
+ margin: 0,
17
+ position: 'relative',
18
+ overflow: 'visible'
16
19
  }
17
20
  })
18
21
 
@@ -41,6 +44,8 @@ const Listbox = React.forwardRef(
41
44
  LinkRouter,
42
45
  itemRouterProps,
43
46
  onClose,
47
+ copy = 'en',
48
+ dictionary = defaultDictionary,
44
49
  variant,
45
50
  tokens,
46
51
  testID
@@ -49,6 +54,7 @@ const Listbox = React.forwardRef(
49
54
  ) => {
50
55
  const initialOpen = getInitialOpen(items, defaultSelectedId)
51
56
  const [selectedId, setSelectedId] = React.useState(defaultSelectedId)
57
+ const [activeSecondLevelGroup, setActiveSecondLevelGroup] = React.useState(null)
52
58
  const listboxTokens = useThemeTokens('Listbox', tokens, variant)
53
59
 
54
60
  // We need to keep track of each item's ref in order to be able to
@@ -102,8 +108,15 @@ const Listbox = React.forwardRef(
102
108
  return () => {}
103
109
  }, [onClose, handleKeydown])
104
110
 
111
+ const contextValue = {
112
+ selectedId,
113
+ setSelectedId,
114
+ activeSecondLevelGroup,
115
+ setActiveSecondLevelGroup
116
+ }
117
+
105
118
  return (
106
- <ListboxContext.Provider value={{ selectedId, setSelectedId }}>
119
+ <ListboxContext.Provider value={contextValue}>
107
120
  <ExpandCollapse initialOpen={initialOpen} maxOpen={1} ref={ref}>
108
121
  {(expandProps) => (
109
122
  <View
@@ -121,6 +134,10 @@ const Listbox = React.forwardRef(
121
134
  return currentItemRef
122
135
  }
123
136
 
137
+ if (!nestedItems && activeSecondLevelGroup) {
138
+ return null
139
+ }
140
+
124
141
  return nestedItems ? (
125
142
  <ListboxGroup
126
143
  {...item}
@@ -131,6 +148,11 @@ const Listbox = React.forwardRef(
131
148
  nextItemRef={itemRefs.current[index + 1] ?? null}
132
149
  ref={index === 0 ? firstItemRef : itemRef}
133
150
  key={itemId}
151
+ copy={copy}
152
+ dictionary={dictionary}
153
+ variant={variant}
154
+ tokens={tokens}
155
+ onClose={onClose}
134
156
  />
135
157
  ) : (
136
158
  <ListboxItem
@@ -142,6 +164,8 @@ const Listbox = React.forwardRef(
142
164
  prevItemRef={itemRefs.current[index - 1] ?? null}
143
165
  nextItemRef={itemRefs.current[index + 1] ?? null}
144
166
  ref={index === 0 ? firstItemRef : itemRef}
167
+ variant={variant}
168
+ tokens={tokens}
145
169
  />
146
170
  )
147
171
  })}
@@ -183,6 +207,21 @@ Listbox.propTypes = {
183
207
  * Test ID for testing
184
208
  */
185
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
+ }),
186
225
  /**
187
226
  * Listbox variant
188
227
  */
@@ -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,
@@ -79,26 +175,31 @@ const ListboxGroup = React.forwardRef(
79
175
  }}
80
176
  controlRef={ref}
81
177
  >
82
- <View style={styles.list}>
83
- {items.map((item, index) => {
84
- return (
85
- <ListboxItem
86
- key={item.label}
87
- id={item.id ?? item.label}
88
- {...item}
89
- selected={
90
- (item.id && item.id === selectedId) || (item.label && item.label === selectedId)
91
- }
92
- isChild
93
- LinkRouter={LinkRouter}
94
- linkRouterProps={linkRouterProps}
95
- {...(index === 0 && { prevItemRef })}
96
- {...(index === items.length - 1 && { nextItemRef })}
97
- {...(index === items.length - 1 && { onBlur: onLastItemBlur })}
98
- />
99
- )
100
- })}
101
- </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
+ )}
102
203
  </ExpandCollapse.Panel>
103
204
  </View>
104
205
  )
@@ -108,6 +209,10 @@ ListboxGroup.displayName = 'ListboxGroup'
108
209
 
109
210
  ListboxGroup.propTypes = {
110
211
  ...withLinkRouter.propTypes,
212
+ /**
213
+ * Unique identifier for the group
214
+ */
215
+ id: PropTypes.string,
111
216
  label: PropTypes.string,
112
217
  items: PropTypes.arrayOf(
113
218
  PropTypes.shape({
@@ -122,7 +227,34 @@ ListboxGroup.propTypes = {
122
227
  /**
123
228
  * Use this callback to redirect the focus after it leaves the last item of the group.
124
229
  */
125
- 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
126
258
  }
127
259
 
128
260
  export default ListboxGroup
@@ -23,10 +23,21 @@ const paddingHorizontal = 0
23
23
 
24
24
  const DropdownOverlay = React.forwardRef(
25
25
  (
26
- { children, isReady = false, overlaidPosition, maxWidth, minWidth, onLayout, tokens, testID },
26
+ {
27
+ children,
28
+ isReady = false,
29
+ overlaidPosition,
30
+ maxWidth,
31
+ minWidth,
32
+ onLayout,
33
+ tokens,
34
+ testID,
35
+ variant
36
+ },
37
+
27
38
  ref
28
39
  ) => {
29
- const systemTokens = useThemeTokens('Listbox', {}, {})
40
+ const systemTokens = useThemeTokens('Listbox', variant, tokens)
30
41
 
31
42
  return (
32
43
  <View
@@ -43,11 +54,12 @@ const DropdownOverlay = React.forwardRef(
43
54
  <Card
44
55
  tokens={{
45
56
  shadow: systemTokens.shadow,
57
+ borderRadius: systemTokens.borderRadius,
58
+ ...(Platform.OS === 'web' && { overflowY: 'hidden' }),
46
59
  paddingBottom: paddingVertical,
47
60
  paddingTop: paddingVertical,
48
61
  paddingLeft: paddingHorizontal,
49
- paddingRight: paddingHorizontal,
50
- ...tokens
62
+ paddingRight: paddingHorizontal
51
63
  }}
52
64
  >
53
65
  {children}
@@ -80,7 +92,8 @@ DropdownOverlay.propTypes = {
80
92
  minWidth: PropTypes.number,
81
93
  onLayout: PropTypes.func,
82
94
  tokens: PropTypes.object,
83
- testID: PropTypes.string
95
+ testID: PropTypes.string,
96
+ variant: PropTypes.object
84
97
  }
85
98
 
86
99
  export default Platform.OS === 'web' ? withPortal(DropdownOverlay) : DropdownOverlay
@@ -0,0 +1,182 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { View, StyleSheet, Text, Pressable } from 'react-native'
4
+ import { useThemeTokens } from '../ThemeProvider'
5
+ import { useCopy, variantProp, copyPropTypes } from '../utils'
6
+ import Icon from '../Icon'
7
+ import IconButton from '../IconButton'
8
+ import Divider from '../Divider'
9
+ import defaultDictionary from './dictionary'
10
+
11
+ const styles = StyleSheet.create({
12
+ headerContainer: {
13
+ width: '100%'
14
+ },
15
+ headerContent: {
16
+ flexDirection: 'row',
17
+ alignItems: 'center',
18
+ width: '100%'
19
+ },
20
+ leftSection: {
21
+ flexDirection: 'row',
22
+ alignItems: 'center',
23
+ flex: 1
24
+ },
25
+ backIcon: {
26
+ marginRight: 8,
27
+ flexShrink: 0
28
+ },
29
+ labelText: {
30
+ flex: 1
31
+ },
32
+ closeButton: {
33
+ flexShrink: 0
34
+ },
35
+ dividerContainer: {
36
+ width: '100%'
37
+ }
38
+ })
39
+
40
+ const selectHeaderContainerStyles = ({ secondLevelHeaderBackgroundColor }) => ({
41
+ backgroundColor: secondLevelHeaderBackgroundColor
42
+ })
43
+
44
+ const selectHeaderContentStyles = ({
45
+ secondLevelHeaderPaddingTop,
46
+ secondLevelHeaderPaddingBottom,
47
+ secondLevelHeaderPaddingLeft,
48
+ secondLevelHeaderPaddingRight
49
+ }) => ({
50
+ paddingTop: secondLevelHeaderPaddingTop,
51
+ paddingBottom: secondLevelHeaderPaddingBottom,
52
+ paddingLeft: secondLevelHeaderPaddingLeft,
53
+ paddingRight: secondLevelHeaderPaddingRight
54
+ })
55
+
56
+ const selectLabelTextStyles = ({
57
+ secondLevelBackLinkFontName,
58
+ secondLevelBackLinkFontWeight,
59
+ secondLevelBackLinkFontSize,
60
+ secondLevelBackLinkColor
61
+ }) => ({
62
+ fontFamily: `${secondLevelBackLinkFontName}${secondLevelBackLinkFontWeight}normal`,
63
+ fontSize: secondLevelBackLinkFontSize,
64
+ color: secondLevelBackLinkColor
65
+ })
66
+
67
+ /**
68
+ * SecondLevelHeader component for Listbox secondLevel variant.
69
+ * Displays a header with back button icon, title text, and close button (IconButton),
70
+ * separated from content by a Divider.
71
+ */
72
+ const SecondLevelHeader = React.forwardRef(
73
+ (
74
+ {
75
+ label,
76
+ onBack,
77
+ onClose,
78
+ copy = 'en',
79
+ dictionary = defaultDictionary,
80
+ tokens: tokensProp = {},
81
+ variant = {}
82
+ },
83
+ ref
84
+ ) => {
85
+ const tokens = useThemeTokens('Listbox', variant, tokensProp)
86
+ const getCopy = useCopy({ dictionary, copy })
87
+
88
+ const {
89
+ secondLevelBackIcon,
90
+ secondLevelBackIconColor,
91
+ secondLevelCloseIcon,
92
+ secondLevelCloseIconSize,
93
+ secondLevelCloseButtonBorderWidth,
94
+ secondLevelCloseButtonPadding,
95
+ secondLevelDividerColor,
96
+ secondLevelDividerWidth
97
+ } = tokens
98
+
99
+ return (
100
+ <View style={[styles.headerContainer, selectHeaderContainerStyles(tokens)]} ref={ref}>
101
+ <View style={[styles.headerContent, selectHeaderContentStyles(tokens)]}>
102
+ <Pressable onPress={onBack} style={styles.leftSection}>
103
+ <View style={styles.backIcon}>
104
+ <Icon
105
+ icon={secondLevelBackIcon}
106
+ tokens={{
107
+ color: secondLevelBackIconColor
108
+ }}
109
+ variant={{ size: 'micro' }}
110
+ />
111
+ </View>
112
+ <Text numberOfLines={1} style={[styles.labelText, selectLabelTextStyles(tokens)]}>
113
+ {label}
114
+ </Text>
115
+ </Pressable>
116
+ <View style={styles.closeButton}>
117
+ <IconButton
118
+ icon={secondLevelCloseIcon}
119
+ onPress={onClose}
120
+ accessibilityLabel={getCopy('closeMenu')}
121
+ tokens={{
122
+ iconSize: secondLevelCloseIconSize,
123
+ borderWidth: secondLevelCloseButtonBorderWidth,
124
+ padding: secondLevelCloseButtonPadding
125
+ }}
126
+ />
127
+ </View>
128
+ </View>
129
+ <View style={styles.dividerContainer}>
130
+ <Divider
131
+ tokens={{
132
+ color: secondLevelDividerColor,
133
+ width: secondLevelDividerWidth
134
+ }}
135
+ />
136
+ </View>
137
+ </View>
138
+ )
139
+ }
140
+ )
141
+
142
+ SecondLevelHeader.displayName = 'SecondLevelHeader'
143
+
144
+ SecondLevelHeader.propTypes = {
145
+ /**
146
+ * The label text to display (typically the parent item label)
147
+ */
148
+ label: PropTypes.string.isRequired,
149
+ /**
150
+ * Callback when back button is clicked
151
+ */
152
+ onBack: PropTypes.func.isRequired,
153
+ /**
154
+ * Callback when close button is clicked
155
+ */
156
+ onClose: PropTypes.func.isRequired,
157
+ /**
158
+ * Select English or French copy
159
+ */
160
+ copy: copyPropTypes,
161
+ /**
162
+ * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
163
+ */
164
+ dictionary: PropTypes.shape({
165
+ en: PropTypes.shape({
166
+ closeMenu: PropTypes.string.isRequired
167
+ }),
168
+ fr: PropTypes.shape({
169
+ closeMenu: PropTypes.string.isRequired
170
+ })
171
+ }),
172
+ /**
173
+ * Custom tokens to override theme tokens
174
+ */
175
+ tokens: PropTypes.object,
176
+ /**
177
+ * Variant configuration
178
+ */
179
+ variant: variantProp.propType
180
+ }
181
+
182
+ export default SecondLevelHeader
@@ -0,0 +1,8 @@
1
+ export default {
2
+ en: {
3
+ closeMenu: 'Close menu'
4
+ },
5
+ fr: {
6
+ closeMenu: 'Fermer le menu'
7
+ }
8
+ }