@telus-uds/components-base 3.12.2 → 3.14.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 (63) hide show
  1. package/CHANGELOG.md +37 -2
  2. package/lib/cjs/BaseProvider/index.js +4 -1
  3. package/lib/cjs/Button/ButtonDropdown.js +105 -12
  4. package/lib/cjs/Card/Card.js +23 -4
  5. package/lib/cjs/Card/CardBase.js +170 -19
  6. package/lib/cjs/Card/PressableCardBase.js +19 -5
  7. package/lib/cjs/Card/backgroundImageStylesMap.js +197 -0
  8. package/lib/cjs/ExpandCollapse/ExpandCollapse.js +3 -1
  9. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMini.js +1 -1
  10. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +30 -6
  11. package/lib/cjs/FlexGrid/FlexGrid.js +71 -6
  12. package/lib/cjs/Icon/Icon.js +3 -1
  13. package/lib/cjs/InputLabel/InputLabel.js +1 -1
  14. package/lib/cjs/InputSupports/InputSupports.js +1 -1
  15. package/lib/cjs/Notification/Notification.js +27 -8
  16. package/lib/cjs/Tabs/Tabs.js +34 -2
  17. package/lib/cjs/Tabs/TabsDropdown.js +252 -0
  18. package/lib/cjs/Tabs/TabsItem.js +4 -2
  19. package/lib/cjs/Tabs/dictionary.js +14 -0
  20. package/lib/cjs/ViewportProvider/ViewportProvider.js +9 -3
  21. package/lib/cjs/utils/props/inputSupportsProps.js +1 -1
  22. package/lib/esm/BaseProvider/index.js +4 -1
  23. package/lib/esm/Button/ButtonDropdown.js +107 -14
  24. package/lib/esm/Card/Card.js +21 -4
  25. package/lib/esm/Card/CardBase.js +169 -19
  26. package/lib/esm/Card/PressableCardBase.js +19 -5
  27. package/lib/esm/Card/backgroundImageStylesMap.js +190 -0
  28. package/lib/esm/ExpandCollapse/ExpandCollapse.js +4 -2
  29. package/lib/esm/ExpandCollapseMini/ExpandCollapseMini.js +2 -2
  30. package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +30 -6
  31. package/lib/esm/FlexGrid/FlexGrid.js +72 -7
  32. package/lib/esm/Icon/Icon.js +3 -1
  33. package/lib/esm/InputLabel/InputLabel.js +1 -1
  34. package/lib/esm/InputSupports/InputSupports.js +1 -1
  35. package/lib/esm/Notification/Notification.js +27 -8
  36. package/lib/esm/Tabs/Tabs.js +35 -3
  37. package/lib/esm/Tabs/TabsDropdown.js +245 -0
  38. package/lib/esm/Tabs/TabsItem.js +4 -2
  39. package/lib/esm/Tabs/dictionary.js +8 -0
  40. package/lib/esm/ViewportProvider/ViewportProvider.js +9 -3
  41. package/lib/esm/utils/props/inputSupportsProps.js +1 -1
  42. package/lib/package.json +2 -2
  43. package/package.json +2 -2
  44. package/src/BaseProvider/index.jsx +4 -2
  45. package/src/Button/ButtonDropdown.jsx +109 -16
  46. package/src/Card/Card.jsx +27 -3
  47. package/src/Card/CardBase.jsx +165 -19
  48. package/src/Card/PressableCardBase.jsx +31 -4
  49. package/src/Card/backgroundImageStylesMap.js +41 -0
  50. package/src/ExpandCollapse/ExpandCollapse.jsx +5 -2
  51. package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +2 -2
  52. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +39 -9
  53. package/src/FlexGrid/FlexGrid.jsx +80 -7
  54. package/src/Icon/Icon.jsx +3 -1
  55. package/src/InputLabel/InputLabel.jsx +1 -1
  56. package/src/InputSupports/InputSupports.jsx +1 -1
  57. package/src/Notification/Notification.jsx +58 -9
  58. package/src/Tabs/Tabs.jsx +36 -2
  59. package/src/Tabs/TabsDropdown.jsx +265 -0
  60. package/src/Tabs/TabsItem.jsx +4 -2
  61. package/src/Tabs/dictionary.js +8 -0
  62. package/src/ViewportProvider/ViewportProvider.jsx +8 -3
  63. package/src/utils/props/inputSupportsProps.js +1 -1
@@ -0,0 +1,265 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { Pressable, StyleSheet, Text, View } from 'react-native'
4
+ import { useThemeTokensCallback, applyTextStyles, useTheme } from '../ThemeProvider'
5
+ import {
6
+ a11yProps,
7
+ getTokensPropType,
8
+ resolvePressableTokens,
9
+ selectSystemProps,
10
+ selectTokens,
11
+ useOverlaidPosition,
12
+ useCopy,
13
+ variantProp,
14
+ viewProps,
15
+ withLinkRouter
16
+ } from '../utils'
17
+ import { useViewport } from '../ViewportProvider'
18
+ import Icon from '../Icon'
19
+ import Listbox from '../Listbox'
20
+ import dictionary from './dictionary'
21
+
22
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
23
+
24
+ const selectButtonContentStyles = ({
25
+ backgroundColor,
26
+ borderColor,
27
+ borderWidth,
28
+ borderRadius,
29
+ paddingHorizontal,
30
+ paddingVertical,
31
+ marginHorizontal,
32
+ marginVertical
33
+ }) => ({
34
+ backgroundColor,
35
+ borderColor,
36
+ borderWidth,
37
+ borderRadius,
38
+ paddingHorizontal,
39
+ paddingVertical,
40
+ marginLeft: marginHorizontal,
41
+ marginRight: marginHorizontal,
42
+ marginTop: marginVertical,
43
+ marginBottom: marginVertical
44
+ })
45
+
46
+ /**
47
+ * TabsDropdown renders a dropdown version of tabs for mobile/tablet viewports.
48
+ * It shows the currently selected tab as a button that opens a dropdown menu
49
+ * containing all available tabs.
50
+ *
51
+ * This is rendered automatically by `Tabs` on mobile viewports and when variant
52
+ * is dropdown and isn't intended to be used directly.
53
+ */
54
+ const TabsDropdown = React.forwardRef(
55
+ (
56
+ {
57
+ itemTokens,
58
+ variant,
59
+ value,
60
+ onChange,
61
+ items = [],
62
+ LinkRouter,
63
+ linkRouterProps,
64
+ accessibilityRole = 'button',
65
+ copy = 'en',
66
+ dictionary: customDictionary = dictionary,
67
+ ...rest
68
+ },
69
+ ref
70
+ ) => {
71
+ const { themeOptions } = useTheme()
72
+ const viewport = useViewport()
73
+ const [isOpen, setIsOpen] = React.useState(false)
74
+
75
+ const getTokens = useThemeTokensCallback('TabsItem', itemTokens, { viewport, ...variant })
76
+
77
+ const selectedItem =
78
+ items.find((item) => {
79
+ const itemId = item.id ?? item.label
80
+ return value === itemId
81
+ }) || items[0]
82
+
83
+ const { overlaidPosition, sourceRef, targetRef, onTargetLayout, isReady } = useOverlaidPosition(
84
+ {
85
+ isShown: isOpen,
86
+ offsets: { vertical: 4 },
87
+ align: { top: 'bottom', left: 'left' }
88
+ }
89
+ )
90
+
91
+ const handleToggle = () => setIsOpen((prev) => !prev)
92
+ const handleClose = () => setIsOpen(false)
93
+
94
+ const handleItemSelect = (item, event) => {
95
+ const itemId = item.id ?? item.label
96
+ setIsOpen(false)
97
+
98
+ if (onChange) onChange(itemId, event)
99
+ if (item.onPress) item.onPress(event)
100
+ }
101
+
102
+ const listboxItems = items.map((item) => ({
103
+ ...item,
104
+ onPress: (event) => handleItemSelect(item, event)
105
+ }))
106
+
107
+ const isSelected = Boolean(selectedItem && value)
108
+ const getCopy = useCopy({ dictionary: customDictionary, copy })
109
+
110
+ const selectedProps = selectProps({
111
+ accessibilityRole,
112
+ ...rest
113
+ })
114
+
115
+ return (
116
+ <View ref={ref} style={styles.container}>
117
+ <Pressable
118
+ ref={sourceRef}
119
+ onPress={handleToggle}
120
+ {...selectedProps}
121
+ style={styles.pressable}
122
+ >
123
+ {(pressableState) => {
124
+ // Use resolvePressableTokens like TabBarItem does for proper state handling
125
+ const resolvedTokens = resolvePressableTokens(getTokens, pressableState, {
126
+ viewport,
127
+ expanded: isOpen,
128
+ selected: isSelected
129
+ })
130
+
131
+ const textStyles = applyTextStyles({
132
+ ...selectTokens('Typography', resolvedTokens),
133
+ themeOptions
134
+ })
135
+
136
+ // Get dropdown icons from resolved tokens
137
+ const dropdownIcon = isOpen
138
+ ? resolvedTokens.dropdownIconExpanded
139
+ : resolvedTokens.dropdownIcon
140
+
141
+ return (
142
+ <View style={[styles.buttonContent, selectButtonContentStyles(resolvedTokens)]}>
143
+ <Text style={textStyles}>{selectedItem?.label || getCopy('selectTab')}</Text>
144
+ {dropdownIcon && (
145
+ <Icon
146
+ icon={dropdownIcon}
147
+ variant={{ size: 'micro' }}
148
+ tokens={{
149
+ color: textStyles.color
150
+ }}
151
+ />
152
+ )}
153
+ </View>
154
+ )
155
+ }}
156
+ </Pressable>
157
+
158
+ {isOpen && (
159
+ <Listbox.Overlay
160
+ overlaidPosition={overlaidPosition}
161
+ maxWidth={400}
162
+ minWidth={200}
163
+ isReady={isReady}
164
+ onLayout={onTargetLayout}
165
+ >
166
+ <Listbox
167
+ items={listboxItems}
168
+ firstItemRef={targetRef}
169
+ parentRef={sourceRef}
170
+ selectedId={value}
171
+ onClose={handleClose}
172
+ LinkRouter={LinkRouter}
173
+ linkRouterProps={linkRouterProps}
174
+ />
175
+ </Listbox.Overlay>
176
+ )}
177
+ </View>
178
+ )
179
+ }
180
+ )
181
+
182
+ TabsDropdown.displayName = 'TabsDropdown'
183
+
184
+ const dictionaryContentShape = PropTypes.shape({
185
+ selectTab: PropTypes.string.isRequired
186
+ })
187
+
188
+ TabsDropdown.propTypes = {
189
+ ...selectedSystemPropTypes,
190
+ ...withLinkRouter.propTypes,
191
+ /**
192
+ * Array of tab items
193
+ */
194
+ items: PropTypes.arrayOf(
195
+ PropTypes.shape({
196
+ ...withLinkRouter.propTypes,
197
+ /** URL to navigate to when the tab is pressed */
198
+ href: PropTypes.string,
199
+ /** Display text for the tab */
200
+ label: PropTypes.string,
201
+ /** Unique identifier for the tab */
202
+ id: PropTypes.string,
203
+ /** Reference to the tab element */
204
+ ref: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
205
+ /** Custom render function for the tab content */
206
+ render: PropTypes.func
207
+ })
208
+ ),
209
+ /**
210
+ * Current selected tab id
211
+ */
212
+ value: PropTypes.string,
213
+ /**
214
+ * Callback for when the selected tab changes
215
+ */
216
+ onChange: PropTypes.func,
217
+ /**
218
+ * Custom tokens for the main Tabs container
219
+ */
220
+ tokens: getTokensPropType('Tabs'),
221
+ /**
222
+ * Custom tokens for `TabsItem`
223
+ */
224
+ itemTokens: getTokensPropType('TabsItem'),
225
+ /**
226
+ * Visual and behavioral variants for the tabs dropdown
227
+ */
228
+ variant: variantProp.propType,
229
+ /**
230
+ * Select English or French copy for the accessible labels.
231
+ * You may also pass in a custom dictionary object.
232
+ */
233
+ copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), dictionaryContentShape]),
234
+ /**
235
+ * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
236
+ */
237
+ dictionary: PropTypes.shape({
238
+ en: dictionaryContentShape,
239
+ fr: dictionaryContentShape
240
+ })
241
+ }
242
+
243
+ const styles = StyleSheet.create({
244
+ container: {
245
+ position: 'relative',
246
+ width: '100%'
247
+ },
248
+ pressable: {
249
+ outlineWidth: 0,
250
+ outlineStyle: 'none',
251
+ outlineColor: 'transparent'
252
+ },
253
+ buttonContent: {
254
+ display: 'flex',
255
+ flexDirection: 'row',
256
+ alignItems: 'center',
257
+ justifyContent: 'space-between',
258
+ width: '100%',
259
+ minHeight: 44,
260
+ outline: 'none',
261
+ boxSizing: 'border-box'
262
+ }
263
+ })
264
+
265
+ export default TabsDropdown
@@ -70,8 +70,10 @@ const selectContainerStyles = ({
70
70
  borderRadius,
71
71
  paddingHorizontal: paddingHorizontal - borderWidth,
72
72
  paddingVertical: paddingVertical - borderWidth,
73
- marginHorizontal,
74
- marginVertical
73
+ marginLeft: marginHorizontal,
74
+ marginRight: marginHorizontal,
75
+ marginTop: marginVertical,
76
+ marginBottom: marginVertical
75
77
  })
76
78
 
77
79
  /**
@@ -0,0 +1,8 @@
1
+ export default {
2
+ en: {
3
+ selectTab: 'Select tab'
4
+ },
5
+ fr: {
6
+ selectTab: 'Sélectionner un onglet'
7
+ }
8
+ }
@@ -6,16 +6,21 @@ import useViewportListener from './useViewportListener'
6
6
 
7
7
  /**
8
8
  * Provides an up-to-date viewport value from system-constants, available via the `useViewport` hook
9
+ *
10
+ * @param {React.ReactNode} children - Child components that will have access to viewport context
11
+ * @param {string} [defaultViewport] - Default viewport to use during server-side rendering.
12
+ * Must be one of the viewport keys from system-constants. If not provided, defaults to the smallest viewport.
9
13
  */
10
- const ViewportProvider = ({ children }) => {
14
+ const ViewportProvider = ({ children, defaultViewport }) => {
11
15
  // Default to the smallest viewport for mobile-first SSR. On client side, this is updated
12
16
  // by useViewportListener in a layout effect before anything is shown to the user.
13
- const [viewport, setViewport] = React.useState(viewports.keys[0])
17
+ const [viewport, setViewport] = React.useState(defaultViewport || viewports.keys[0])
14
18
  useViewportListener(setViewport)
15
19
  return <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>
16
20
  }
17
21
  ViewportProvider.propTypes = {
18
- children: PropTypes.node.isRequired
22
+ children: PropTypes.node.isRequired,
23
+ defaultViewport: PropTypes.oneOf(viewports.keys)
19
24
  }
20
25
 
21
26
  export default ViewportProvider
@@ -38,7 +38,7 @@ export default {
38
38
  * 1. `tooltip` as a string - The content of the tooltip.
39
39
  * 2. `tooltip` as an object - Tooltip component props to be passed.
40
40
  */
41
- tooltip: PropTypes.oneOfType([tooltipPropTypes, PropTypes.string]),
41
+ tooltip: PropTypes.oneOfType([PropTypes.shape(tooltipPropTypes), PropTypes.string]),
42
42
  /**
43
43
  * Use to visually mark an input as valid or invalid.
44
44
  */