@telus-uds/components-base 3.23.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.
- package/CHANGELOG.md +12 -1
- package/lib/cjs/Card/CardBase.js +97 -17
- package/lib/cjs/Card/PressableCardBase.js +12 -8
- package/lib/cjs/HorizontalScroll/HorizontalScroll.js +5 -2
- package/lib/cjs/Icon/Icon.js +3 -0
- package/lib/cjs/Listbox/GroupControl.js +12 -6
- package/lib/cjs/Listbox/Listbox.js +41 -7
- package/lib/cjs/Listbox/ListboxGroup.js +139 -8
- package/lib/cjs/Listbox/ListboxOverlay.js +10 -5
- package/lib/cjs/Listbox/SecondLevelHeader.js +201 -0
- package/lib/cjs/Listbox/dictionary.js +14 -0
- package/lib/cjs/Shortcuts/Shortcuts.js +169 -0
- package/lib/cjs/Shortcuts/ShortcutsItem.js +280 -0
- package/lib/cjs/Shortcuts/index.js +16 -0
- package/lib/cjs/Tooltip/Tooltip.native.js +2 -0
- package/lib/cjs/index.js +15 -0
- package/lib/esm/Card/CardBase.js +97 -17
- package/lib/esm/Card/PressableCardBase.js +10 -8
- package/lib/esm/HorizontalScroll/HorizontalScroll.js +6 -3
- package/lib/esm/Icon/Icon.js +3 -0
- package/lib/esm/Listbox/GroupControl.js +12 -6
- package/lib/esm/Listbox/Listbox.js +41 -7
- package/lib/esm/Listbox/ListboxGroup.js +141 -10
- package/lib/esm/Listbox/ListboxOverlay.js +10 -5
- package/lib/esm/Listbox/SecondLevelHeader.js +194 -0
- package/lib/esm/Listbox/dictionary.js +8 -0
- package/lib/esm/Shortcuts/Shortcuts.js +160 -0
- package/lib/esm/Shortcuts/ShortcutsItem.js +273 -0
- package/lib/esm/Shortcuts/index.js +3 -0
- package/lib/esm/Tooltip/Tooltip.native.js +2 -0
- package/lib/esm/index.js +1 -0
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/src/Card/CardBase.jsx +113 -14
- package/src/Card/PressableCardBase.jsx +17 -5
- package/src/HorizontalScroll/HorizontalScroll.jsx +6 -3
- package/src/Icon/Icon.jsx +3 -0
- package/src/Listbox/GroupControl.jsx +41 -33
- package/src/Listbox/Listbox.jsx +41 -2
- package/src/Listbox/ListboxGroup.jsx +158 -26
- package/src/Listbox/ListboxOverlay.jsx +18 -5
- package/src/Listbox/SecondLevelHeader.jsx +182 -0
- package/src/Listbox/dictionary.js +8 -0
- package/src/Shortcuts/Shortcuts.jsx +174 -0
- package/src/Shortcuts/ShortcutsItem.jsx +297 -0
- package/src/Shortcuts/index.js +4 -0
- package/src/Tooltip/Tooltip.native.jsx +2 -1
- package/src/index.js +1 -0
- package/types/Listbox.d.ts +24 -0
- package/types/Shortcuts.d.ts +136 -0
- package/types/index.d.ts +12 -0
package/src/Card/CardBase.jsx
CHANGED
|
@@ -6,9 +6,18 @@ import { applyShadowToken } from '../ThemeProvider'
|
|
|
6
6
|
import { getTokensPropType, responsiveProps, useResponsiveProp, formatImageSource } from '../utils'
|
|
7
7
|
import { a11yProps, viewProps, selectSystemProps } from '../utils/props'
|
|
8
8
|
import backgroundImageStylesMap from './backgroundImageStylesMap'
|
|
9
|
+
import FlexGrid from '../FlexGrid/FlexGrid'
|
|
10
|
+
import FlexGridRow from '../FlexGrid/Row'
|
|
11
|
+
import FlexGridCol from '../FlexGrid/Col'
|
|
9
12
|
|
|
10
13
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
|
|
11
14
|
|
|
15
|
+
const GRID_COLUMNS = 12
|
|
16
|
+
|
|
17
|
+
const isOverlayColor = (color) => {
|
|
18
|
+
return color && typeof color === 'string' && color.startsWith('rgba(')
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
const setBackgroundImage = ({
|
|
13
22
|
src,
|
|
14
23
|
alt,
|
|
@@ -22,12 +31,10 @@ const setBackgroundImage = ({
|
|
|
22
31
|
const borderWidth = cardStyle?.borderWidth || 0
|
|
23
32
|
const adjustedBorderRadius = Math.max(0, borderRadius - borderWidth)
|
|
24
33
|
|
|
25
|
-
// For contain mode with position and align, use CSS background properties for web
|
|
26
34
|
if (backgroundImageResizeMode === 'contain' && backgroundImagePosition && backgroundImageAlign) {
|
|
27
35
|
const positionKey = `${backgroundImagePosition}-${backgroundImageAlign}`
|
|
28
36
|
|
|
29
37
|
if (Platform.OS === 'web') {
|
|
30
|
-
// Create background position based on position and align
|
|
31
38
|
let backgroundPosition
|
|
32
39
|
|
|
33
40
|
switch (positionKey) {
|
|
@@ -77,7 +84,7 @@ const setBackgroundImage = ({
|
|
|
77
84
|
</View>
|
|
78
85
|
)
|
|
79
86
|
}
|
|
80
|
-
|
|
87
|
+
|
|
81
88
|
const positionStyles = backgroundImageStylesMap[positionKey] || {}
|
|
82
89
|
|
|
83
90
|
return (
|
|
@@ -95,7 +102,6 @@ const setBackgroundImage = ({
|
|
|
95
102
|
)
|
|
96
103
|
}
|
|
97
104
|
|
|
98
|
-
// Use ImageBackground for all other resize modes and React Native
|
|
99
105
|
return (
|
|
100
106
|
<ImageBackground
|
|
101
107
|
source={src}
|
|
@@ -121,6 +127,10 @@ export const selectStyles = ({
|
|
|
121
127
|
paddingLeft,
|
|
122
128
|
paddingRight,
|
|
123
129
|
paddingTop,
|
|
130
|
+
marginTop,
|
|
131
|
+
marginBottom,
|
|
132
|
+
marginLeft,
|
|
133
|
+
marginRight,
|
|
124
134
|
minWidth,
|
|
125
135
|
shadow,
|
|
126
136
|
backgroundGradient,
|
|
@@ -128,9 +138,27 @@ export const selectStyles = ({
|
|
|
128
138
|
maxHeight,
|
|
129
139
|
overflowY
|
|
130
140
|
}) => {
|
|
141
|
+
const hasGradient = (gradient || backgroundGradient) && Platform.OS === 'web'
|
|
142
|
+
|
|
143
|
+
let backgroundImageValue = null
|
|
144
|
+
|
|
145
|
+
if (hasGradient) {
|
|
146
|
+
const gradientObj = gradient || backgroundGradient
|
|
147
|
+
const gradientString = `linear-gradient(${gradientObj.angle}deg, ${gradientObj.stops[0].color}, ${gradientObj.stops[1].color})`
|
|
148
|
+
const shouldApplyOverlay =
|
|
149
|
+
(gradient || (backgroundGradient && backgroundColor && backgroundColor !== 'transparent')) &&
|
|
150
|
+
isOverlayColor(backgroundColor)
|
|
151
|
+
|
|
152
|
+
backgroundImageValue = shouldApplyOverlay
|
|
153
|
+
? `linear-gradient(${backgroundColor}, ${backgroundColor}), ${gradientString}`
|
|
154
|
+
: gradientString
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const boxShadowColor = isOverlayColor(backgroundColor) ? backgroundColor : 'white'
|
|
158
|
+
|
|
131
159
|
return {
|
|
132
160
|
flex,
|
|
133
|
-
backgroundColor,
|
|
161
|
+
backgroundColor: hasGradient ? 'transparent' : backgroundColor,
|
|
134
162
|
borderColor,
|
|
135
163
|
borderRadius,
|
|
136
164
|
borderWidth,
|
|
@@ -138,19 +166,23 @@ export const selectStyles = ({
|
|
|
138
166
|
paddingLeft,
|
|
139
167
|
paddingRight,
|
|
140
168
|
paddingTop,
|
|
169
|
+
marginTop,
|
|
170
|
+
marginBottom,
|
|
171
|
+
marginLeft,
|
|
172
|
+
marginRight,
|
|
141
173
|
minWidth,
|
|
142
174
|
...applyShadowToken(shadow),
|
|
143
175
|
...(gradient && Platform.OS === 'web'
|
|
144
176
|
? {
|
|
145
|
-
backgroundImage:
|
|
177
|
+
backgroundImage: backgroundImageValue,
|
|
146
178
|
backgroundOrigin: `border-box`,
|
|
147
|
-
boxShadow: `inset 0 1000px
|
|
179
|
+
boxShadow: `inset 0 1000px ${boxShadowColor}`,
|
|
148
180
|
border: `${borderWidth}px solid transparent`
|
|
149
181
|
}
|
|
150
182
|
: {}),
|
|
151
183
|
...(backgroundGradient && Platform.OS === 'web'
|
|
152
184
|
? {
|
|
153
|
-
backgroundImage:
|
|
185
|
+
backgroundImage: backgroundImageValue
|
|
154
186
|
}
|
|
155
187
|
: {}),
|
|
156
188
|
...(Platform.OS === 'web' ? { maxHeight, overflowY } : {})
|
|
@@ -162,8 +194,8 @@ export const selectStyles = ({
|
|
|
162
194
|
* intended to be used in apps or sites directly: build themed components on top of this.
|
|
163
195
|
*/
|
|
164
196
|
const CardBase = React.forwardRef(
|
|
165
|
-
({ children, tokens, dataSet, backgroundImage, ...rest }, ref) => {
|
|
166
|
-
const cardStyle = selectStyles(typeof tokens === 'function' ? tokens() : tokens)
|
|
197
|
+
({ children, tokens, dataSet, backgroundImage, fullBleedContent, cardState, ...rest }, ref) => {
|
|
198
|
+
const cardStyle = selectStyles(typeof tokens === 'function' ? tokens(cardState) : tokens)
|
|
167
199
|
const props = selectProps(rest)
|
|
168
200
|
|
|
169
201
|
let content = children
|
|
@@ -174,12 +206,16 @@ const CardBase = React.forwardRef(
|
|
|
174
206
|
const backgroundImageAlign = useResponsiveProp(align)
|
|
175
207
|
const imageSourceViewport = formatImageSource(useResponsiveProp(src))
|
|
176
208
|
|
|
209
|
+
const {
|
|
210
|
+
content: fullBleedImageContent,
|
|
211
|
+
position: fullBleedContentPosition,
|
|
212
|
+
imgCol
|
|
213
|
+
} = fullBleedContent || {}
|
|
214
|
+
const fullBleedPosition = useResponsiveProp(fullBleedContentPosition, 'bottom')
|
|
215
|
+
|
|
177
216
|
if (backgroundImage && src) {
|
|
178
|
-
// When there's a background image, separate the padding from the container style
|
|
179
|
-
// so the image can fill the entire container without padding interference
|
|
180
217
|
const { paddingTop, paddingBottom, paddingLeft, paddingRight, ...containerStyle } = cardStyle
|
|
181
218
|
|
|
182
|
-
// Only create padding wrapper if there's actually padding defined
|
|
183
219
|
const hasPadding = paddingTop || paddingBottom || paddingLeft || paddingRight
|
|
184
220
|
const paddedContent = hasPadding ? (
|
|
185
221
|
<View style={{ paddingTop, paddingBottom, paddingLeft, paddingRight }}>{children}</View>
|
|
@@ -204,6 +240,53 @@ const CardBase = React.forwardRef(
|
|
|
204
240
|
)
|
|
205
241
|
}
|
|
206
242
|
|
|
243
|
+
if (fullBleedContent && fullBleedImageContent) {
|
|
244
|
+
const { paddingTop, paddingBottom, paddingLeft, paddingRight, ...containerStyle } = cardStyle
|
|
245
|
+
const imageColumns = imgCol || { xs: GRID_COLUMNS }
|
|
246
|
+
|
|
247
|
+
const textColumns = {}
|
|
248
|
+
Object.keys(imageColumns).forEach((breakpoint) => {
|
|
249
|
+
textColumns[breakpoint] = GRID_COLUMNS - (imageColumns[breakpoint] || GRID_COLUMNS)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
const imageFirst = fullBleedPosition === 'top' || fullBleedPosition === 'left'
|
|
253
|
+
|
|
254
|
+
const imageColContent = (
|
|
255
|
+
<FlexGridCol {...imageColumns} style={staticStyles.fullBleedImageCol}>
|
|
256
|
+
{fullBleedImageContent}
|
|
257
|
+
</FlexGridCol>
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
const textColContent = (
|
|
261
|
+
<FlexGridCol
|
|
262
|
+
{...textColumns}
|
|
263
|
+
style={{ paddingTop, paddingBottom, paddingLeft, paddingRight }}
|
|
264
|
+
>
|
|
265
|
+
{children}
|
|
266
|
+
</FlexGridCol>
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<View style={containerStyle} dataSet={dataSet} ref={ref} {...props}>
|
|
271
|
+
<FlexGrid>
|
|
272
|
+
<FlexGridRow>
|
|
273
|
+
{imageFirst ? (
|
|
274
|
+
<>
|
|
275
|
+
{imageColContent}
|
|
276
|
+
{textColContent}
|
|
277
|
+
</>
|
|
278
|
+
) : (
|
|
279
|
+
<>
|
|
280
|
+
{textColContent}
|
|
281
|
+
{imageColContent}
|
|
282
|
+
</>
|
|
283
|
+
)}
|
|
284
|
+
</FlexGridRow>
|
|
285
|
+
</FlexGrid>
|
|
286
|
+
</View>
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
207
290
|
return (
|
|
208
291
|
<View style={cardStyle} dataSet={dataSet} ref={ref} {...props}>
|
|
209
292
|
{content}
|
|
@@ -213,6 +296,18 @@ const CardBase = React.forwardRef(
|
|
|
213
296
|
)
|
|
214
297
|
CardBase.displayName = 'CardBase'
|
|
215
298
|
|
|
299
|
+
export const fullBleedContentPropTypes = PropTypes.shape({
|
|
300
|
+
content: PropTypes.node.isRequired,
|
|
301
|
+
alt: PropTypes.string,
|
|
302
|
+
position: responsiveProps.getTypeOptionallyByViewport(
|
|
303
|
+
PropTypes.oneOf(['bottom', 'left', 'right', 'top'])
|
|
304
|
+
),
|
|
305
|
+
align: responsiveProps.getTypeOptionallyByViewport(
|
|
306
|
+
PropTypes.oneOf(['start', 'end', 'center', 'stretch'])
|
|
307
|
+
),
|
|
308
|
+
imgCol: PropTypes.object
|
|
309
|
+
})
|
|
310
|
+
|
|
216
311
|
const staticStyles = StyleSheet.create({
|
|
217
312
|
imageBackground: { width: '100%', height: '100%' },
|
|
218
313
|
contentOverlay: {
|
|
@@ -231,6 +326,9 @@ const staticStyles = StyleSheet.create({
|
|
|
231
326
|
position: 'absolute',
|
|
232
327
|
width: '100%',
|
|
233
328
|
height: '100%'
|
|
329
|
+
},
|
|
330
|
+
fullBleedImageCol: {
|
|
331
|
+
padding: 0
|
|
234
332
|
}
|
|
235
333
|
})
|
|
236
334
|
|
|
@@ -255,7 +353,8 @@ CardBase.propTypes = {
|
|
|
255
353
|
align: responsiveProps.getTypeOptionallyByViewport(
|
|
256
354
|
PropTypes.oneOf(['start', 'end', 'center', 'stretch'])
|
|
257
355
|
)
|
|
258
|
-
})
|
|
356
|
+
}),
|
|
357
|
+
fullBleedContent: fullBleedContentPropTypes
|
|
259
358
|
}
|
|
260
359
|
|
|
261
360
|
export default CardBase
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
viewProps,
|
|
19
19
|
withLinkRouter
|
|
20
20
|
} from '../utils'
|
|
21
|
-
import CardBase from './CardBase'
|
|
21
|
+
import CardBase, { fullBleedContentPropTypes } from './CardBase'
|
|
22
22
|
|
|
23
23
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([
|
|
24
24
|
a11yProps,
|
|
@@ -37,6 +37,10 @@ const tokenKeys = [
|
|
|
37
37
|
'paddingLeft',
|
|
38
38
|
'paddingRight',
|
|
39
39
|
'paddingTop',
|
|
40
|
+
'marginTop',
|
|
41
|
+
'marginBottom',
|
|
42
|
+
'marginLeft',
|
|
43
|
+
'marginRight',
|
|
40
44
|
'minWidth',
|
|
41
45
|
'shadow',
|
|
42
46
|
'contentAlignItems',
|
|
@@ -81,6 +85,7 @@ const PressableCardBase = React.forwardRef(
|
|
|
81
85
|
hrefAttrs,
|
|
82
86
|
dataSet,
|
|
83
87
|
backgroundImage,
|
|
88
|
+
fullBleedContent,
|
|
84
89
|
accessibilityRole = href ? 'link' : undefined,
|
|
85
90
|
...rawRest
|
|
86
91
|
},
|
|
@@ -159,13 +164,14 @@ const PressableCardBase = React.forwardRef(
|
|
|
159
164
|
setFocused(false)
|
|
160
165
|
setPressed(false)
|
|
161
166
|
}}
|
|
162
|
-
style={
|
|
167
|
+
style={staticStyles.linkContainer}
|
|
163
168
|
{...(hrefAttrs || {})}
|
|
164
169
|
role={accessibilityRole}
|
|
165
170
|
>
|
|
166
171
|
<CardBase
|
|
167
172
|
tokens={getCardTokens({ pressed, focused, hovered })}
|
|
168
173
|
backgroundImage={backgroundImage}
|
|
174
|
+
fullBleedContent={fullBleedContent}
|
|
169
175
|
>
|
|
170
176
|
{typeof children === 'function'
|
|
171
177
|
? children(getCardState({ pressed, focused, hovered }))
|
|
@@ -188,7 +194,11 @@ const PressableCardBase = React.forwardRef(
|
|
|
188
194
|
{...selectProps({ ...rest, accessibilityRole })}
|
|
189
195
|
>
|
|
190
196
|
{(pressableState) => (
|
|
191
|
-
<CardBase
|
|
197
|
+
<CardBase
|
|
198
|
+
tokens={getCardTokens(pressableState)}
|
|
199
|
+
backgroundImage={backgroundImage}
|
|
200
|
+
fullBleedContent={fullBleedContent}
|
|
201
|
+
>
|
|
192
202
|
{typeof children === 'function' ? children(getCardState(pressableState)) : children}
|
|
193
203
|
</CardBase>
|
|
194
204
|
)}
|
|
@@ -206,7 +216,8 @@ const staticStyles = StyleSheet.create({
|
|
|
206
216
|
flex: 1,
|
|
207
217
|
display: 'flex',
|
|
208
218
|
alignItems: 'stretch',
|
|
209
|
-
justifyContent: 'flex-start'
|
|
219
|
+
justifyContent: 'flex-start',
|
|
220
|
+
textDecorationLine: 'none'
|
|
210
221
|
}
|
|
211
222
|
})
|
|
212
223
|
|
|
@@ -234,7 +245,8 @@ PressableCardBase.propTypes = {
|
|
|
234
245
|
PropTypes.oneOf(['start', 'end', 'center', 'stretch']),
|
|
235
246
|
PropTypes.object
|
|
236
247
|
])
|
|
237
|
-
})
|
|
248
|
+
}),
|
|
249
|
+
fullBleedContent: fullBleedContentPropTypes
|
|
238
250
|
}
|
|
239
251
|
|
|
240
252
|
export default withLinkRouter(PressableCardBase)
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getTokensSetPropType,
|
|
9
9
|
selectSystemProps,
|
|
10
10
|
selectTokens,
|
|
11
|
+
variantProp,
|
|
11
12
|
viewProps
|
|
12
13
|
} from '../utils'
|
|
13
14
|
import ScrollViewEnd from './ScrollViewEnd'
|
|
@@ -38,7 +39,7 @@ const selectBorderStyles = (borderBottomWidth, borderBottomColor) => ({
|
|
|
38
39
|
* @TODO revisit `ScrollButton` after IconButton is stable.
|
|
39
40
|
*/
|
|
40
41
|
const HorizontalScroll = React.forwardRef(
|
|
41
|
-
({ ScrollButton, tokens, itemPositions, children, ...rest }, ref) => {
|
|
42
|
+
({ ScrollButton, tokens, itemPositions, variant, children, ...rest }, ref) => {
|
|
42
43
|
const {
|
|
43
44
|
nextIcon,
|
|
44
45
|
previousIcon,
|
|
@@ -74,8 +75,9 @@ const HorizontalScroll = React.forwardRef(
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
const scrollMax = Math.max(0, contentWidth - containerWidth)
|
|
77
|
-
const
|
|
78
|
-
const
|
|
78
|
+
const hideNavigationButtons = variant?.hideNavigationButtons || false
|
|
79
|
+
const showNextButton = scrollOffset < scrollMax && !hideNavigationButtons
|
|
80
|
+
const showPreviousButton = scrollOffset > 0 && !hideNavigationButtons
|
|
79
81
|
|
|
80
82
|
const scrollRef = React.useRef(null)
|
|
81
83
|
const scrollTo = (targetX) => {
|
|
@@ -173,6 +175,7 @@ HorizontalScroll.propTypes = {
|
|
|
173
175
|
itemPositions: itemPositionsPropType,
|
|
174
176
|
ScrollButton: PropTypes.elementType,
|
|
175
177
|
tokens: getTokensSetPropType(tokenKeys, { allowFunction: true }),
|
|
178
|
+
variant: variantProp.propType,
|
|
176
179
|
children: PropTypes.node
|
|
177
180
|
}
|
|
178
181
|
|
package/src/Icon/Icon.jsx
CHANGED
|
@@ -14,6 +14,7 @@ const Icon = React.forwardRef(
|
|
|
14
14
|
tokens,
|
|
15
15
|
scalesWithText = false,
|
|
16
16
|
style = {},
|
|
17
|
+
testID,
|
|
17
18
|
dataSet
|
|
18
19
|
},
|
|
19
20
|
ref
|
|
@@ -45,6 +46,7 @@ const Icon = React.forwardRef(
|
|
|
45
46
|
const getIconContentForMobile = () => {
|
|
46
47
|
return (
|
|
47
48
|
<View
|
|
49
|
+
testID={testID}
|
|
48
50
|
style={{
|
|
49
51
|
backgroundColor: themeTokens.backgroundColor,
|
|
50
52
|
borderRadius: themeTokens.borderRadius,
|
|
@@ -58,6 +60,7 @@ const Icon = React.forwardRef(
|
|
|
58
60
|
|
|
59
61
|
return Platform.OS === 'web' ? (
|
|
60
62
|
<View
|
|
63
|
+
testID={testID}
|
|
61
64
|
ref={ref}
|
|
62
65
|
// eslint-disable-next-line react-native/no-inline-styles
|
|
63
66
|
style={{
|
|
@@ -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(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
package/src/Listbox/Listbox.jsx
CHANGED
|
@@ -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={
|
|
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
|
*/
|