@telus-uds/components-base 1.50.0 → 1.51.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.
@@ -1,7 +1,7 @@
1
- import React, { useState } from 'react'
1
+ import React, { useState, useEffect } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
- import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider'
4
+ import { useThemeTokens, useThemeTokensCallback, applyTextStyles } from '../ThemeProvider'
5
5
  import {
6
6
  containUniqueFields,
7
7
  getTokensPropType,
@@ -14,7 +14,7 @@ import {
14
14
  variantProp
15
15
  } from '../utils'
16
16
  import dictionary from './dictionary'
17
-
17
+ import { useViewport } from '../ViewportProvider'
18
18
  import Box from '../Box'
19
19
  import { Button, ButtonDropdown } from '../Button'
20
20
  import { CheckboxGroup } from '../Checkbox'
@@ -25,6 +25,7 @@ import StackView from '../StackView'
25
25
  import Typography from '../Typography'
26
26
  import { TextButton } from '../Link'
27
27
  import ModalOverlay from './ModalOverlay'
28
+ import Modal from '../Modal'
28
29
 
29
30
  const { Col, Row } = FlexGrid
30
31
 
@@ -47,6 +48,7 @@ const MultiSelectFilter = ({
47
48
  tokens,
48
49
  items = [],
49
50
  values,
51
+ maxHeight = false,
50
52
  initialValues,
51
53
  maxValues,
52
54
  onChange,
@@ -56,6 +58,7 @@ const MultiSelectFilter = ({
56
58
  rowLimit = 12,
57
59
  ...rest
58
60
  }) => {
61
+ const viewport = useViewport()
59
62
  const { currentValues, setValues } = useMultipleInputValues({
60
63
  initialValues,
61
64
  values,
@@ -63,17 +66,39 @@ const MultiSelectFilter = ({
63
66
  onChange,
64
67
  readOnly
65
68
  })
69
+ const [isOpen, setIsOpen] = useState(false)
70
+ const [checkedIds, setCheckedIds] = useState(currentValues ?? [])
71
+ const [maxWidth, setMaxWidth] = useState(false)
66
72
 
67
73
  const themeTokens = useThemeTokens('ButtonDropdown', tokens, variant)
68
74
  const getItemTokens = useThemeTokensCallback('ButtonDropdown', tokens, variant)
69
75
  const getButtonTokens = (buttonState) => selectTokens('Button', getItemTokens(buttonState))
70
76
  const getCopy = useCopy({ dictionary, copy })
77
+ const colSizeNotMobile = items.length > rowLimit ? 2 : 1
78
+ const colSize = viewport !== 'xs' ? colSizeNotMobile : 1
79
+ const itemsLengthNotMobile = items.length > 24 ? items.length / 2 : rowLimit
80
+ const isSelected = currentValues.length > 0
81
+ const rowLength = viewport !== 'xs' ? itemsLengthNotMobile : items.length
71
82
 
72
- const [isOpen, setIsOpen] = useState(false)
73
- const [checkedIds, setCheckedIds] = useState(currentValues ?? [])
83
+ useEffect(() => {
84
+ if (colSize === 1) return setMaxWidth(false)
85
+ return colSize === 2 && setMaxWidth(true)
86
+ }, [colSize])
74
87
 
75
- const colSize = items.length > rowLimit ? 2 : 1
76
- const isSelected = currentValues.length > 0
88
+ const {
89
+ headerFontColor,
90
+ headerFontSize,
91
+ buttonDirection,
92
+ headerFontWeight,
93
+ headerLineHeight,
94
+ subHeaderFontWeight,
95
+ subHeaderFontSize,
96
+ maxHeightSize,
97
+ maxWidthSize,
98
+ subHeaderLineHeight,
99
+ minHeight,
100
+ minWidth
101
+ } = useThemeTokens('MultiSelectFilter', tokens, { ...variant, maxHeight, maxWidth }, { viewport })
77
102
 
78
103
  const uniqueFields = ['id', 'label']
79
104
  if (!containUniqueFields(items, uniqueFields)) {
@@ -93,14 +118,31 @@ const MultiSelectFilter = ({
93
118
  setIsOpen(false)
94
119
  }
95
120
 
121
+ const onClear = () => {
122
+ setCheckedIds(() => [])
123
+ onApply([])
124
+ }
125
+
96
126
  const { align, offsets } = useResponsiveProp({
97
- xs: { align: { top: 'top', left: 'left', bottom: 'bottom', right: 'right' } },
127
+ xs: { align: { top: 'top', left: 'left' } },
98
128
  sm: {
99
129
  align: { top: 'bottom', left: 'left' },
100
130
  offsets: { vertical: 4 }
101
131
  }
102
132
  })
103
133
 
134
+ const headerStyles = applyTextStyles({
135
+ fontSize: headerFontSize,
136
+ fontWeight: headerFontWeight,
137
+ fontColor: headerFontColor
138
+ })
139
+
140
+ const subeHeaderStyles = applyTextStyles({
141
+ fontSize: subHeaderFontSize,
142
+ fontWeight: subHeaderFontWeight,
143
+ fontColor: selectSubTitleTokens(themeTokens)
144
+ })
145
+
104
146
  const { overlaidPosition, onTargetLayout, isReady, sourceRef } = useOverlaidPosition({
105
147
  isShown: isOpen,
106
148
  offsets,
@@ -120,18 +162,84 @@ const MultiSelectFilter = ({
120
162
  tokens={getButtonTokens}
121
163
  inactive={inactive}
122
164
  />
123
- {isOpen && (
165
+ {isOpen && viewport === 'xs' && (
166
+ <Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
167
+ <Row>
168
+ <Typography tokens={{ ...headerStyles, lineHeight: headerLineHeight }}>
169
+ {getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label.toLowerCase())}
170
+ </Typography>
171
+ </Row>
172
+ {subtitle && (
173
+ <>
174
+ <Spacer space={5} />
175
+ <Row>
176
+ <Typography tokens={{ ...subeHeaderStyles, lineHeight: subHeaderLineHeight }}>
177
+ {subtitle}
178
+ </Typography>
179
+ </Row>
180
+ </>
181
+ )}
182
+ <Spacer space={4} />
183
+ <Box scroll={true}>
184
+ <Row distribute="between">
185
+ {[...Array(colSize).keys()].map((i) => (
186
+ <Col xs={12 / colSize} key={i}>
187
+ <CheckboxGroup
188
+ items={items.slice(i * rowLength, (i + 1) * rowLength)}
189
+ checkedIds={checkedIds}
190
+ onChange={(e) => setCheckedIds(e, i)}
191
+ />
192
+ <Spacer size={4} />
193
+ </Col>
194
+ ))}
195
+ </Row>
196
+ </Box>
197
+ <Divider variant={selectDividerToknes({ ...themeTokens, width: 'full' })} space={4} />
198
+ <Row horizontalAlign={buttonDirection === 'column' ? 'center' : 'start'}>
199
+ <StackView
200
+ direction={buttonDirection}
201
+ space={3}
202
+ tokens={{
203
+ alignItems: 'center',
204
+ ...(viewport === 'xs' && { flexGrow: 1 })
205
+ }}
206
+ >
207
+ <Button
208
+ onPress={() => onApply(checkedIds)}
209
+ variant={{
210
+ size: 'small',
211
+ priority: 'high',
212
+ ...(viewport === 'xs' && { width: 'full' })
213
+ }}
214
+ >
215
+ {getCopy('applyButtonLabel')}
216
+ </Button>
217
+ <Box>
218
+ <TextButton onPress={() => onClear()}>{getCopy('clearButtonLabel')}</TextButton>
219
+ </Box>
220
+ </StackView>
221
+ </Row>
222
+ </Modal>
223
+ )}
224
+ {isOpen && viewport !== 'xs' && (
124
225
  <ModalOverlay
125
226
  overlaidPosition={overlaidPosition}
126
- variant={{ width: colSize > 1 ? 'size576' : 's' }}
127
227
  onClose={() => setIsOpen(false)}
128
- tokens={tokens}
228
+ maxHeight={maxHeight}
229
+ maxHeightSize={maxHeightSize}
230
+ maxWidthSize={maxWidthSize}
231
+ minHeight={minHeight}
232
+ minWidth={minWidth}
233
+ tokens={{
234
+ ...tokens,
235
+ maxWidth
236
+ }}
129
237
  copy={copy}
130
238
  isReady={isReady}
131
239
  onLayout={onTargetLayout}
132
240
  >
133
241
  <Row>
134
- <Typography variant={{ size: 'h4' }}>
242
+ <Typography tokens={{ ...headerStyles, lineHeight: headerLineHeight }}>
135
243
  {getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label.toLowerCase())}
136
244
  </Typography>
137
245
  </Row>
@@ -139,7 +247,7 @@ const MultiSelectFilter = ({
139
247
  <>
140
248
  <Spacer space={5} />
141
249
  <Row>
142
- <Typography variant={{ size: 'h5' }} tokens={selectSubTitleTokens(themeTokens)}>
250
+ <Typography tokens={{ ...subeHeaderStyles, lineHeight: subHeaderLineHeight }}>
143
251
  {subtitle}
144
252
  </Typography>
145
253
  </Row>
@@ -151,7 +259,7 @@ const MultiSelectFilter = ({
151
259
  {[...Array(colSize).keys()].map((i) => (
152
260
  <Col xs={12 / colSize} key={i}>
153
261
  <CheckboxGroup
154
- items={items.slice(i * rowLimit, (i + 1) * rowLimit)}
262
+ items={items.slice(i * rowLength, (i + 1) * rowLength)}
155
263
  checkedIds={checkedIds}
156
264
  onChange={(e) => setCheckedIds(e, i)}
157
265
  />
@@ -161,18 +269,27 @@ const MultiSelectFilter = ({
161
269
  </Row>
162
270
  </Box>
163
271
  <Divider variant={selectDividerToknes({ ...themeTokens, width: 'full' })} space={4} />
164
- <Row>
165
- <StackView direction="row" space={3} tokens={{ alignItems: 'center' }}>
272
+ <Row horizontalAlign={buttonDirection === 'column' ? 'center' : 'start'}>
273
+ <StackView
274
+ direction={buttonDirection}
275
+ space={3}
276
+ tokens={{
277
+ alignItems: 'center',
278
+ ...(viewport === 'xs' && { flexGrow: 1 })
279
+ }}
280
+ >
166
281
  <Button
167
282
  onPress={() => onApply(checkedIds)}
168
- variant={{ size: 'small', priority: 'high' }}
283
+ variant={{
284
+ size: 'small',
285
+ priority: 'high',
286
+ ...(viewport === 'xs' && { width: 'full' })
287
+ }}
169
288
  >
170
289
  {getCopy('applyButtonLabel')}
171
290
  </Button>
172
291
  <Box>
173
- <TextButton onPress={() => setCheckedIds([])}>
174
- {getCopy('clearButtonLabel')}
175
- </TextButton>
292
+ <TextButton onPress={() => onClear()}>{getCopy('clearButtonLabel')}</TextButton>
176
293
  </Box>
177
294
  </StackView>
178
295
  </Row>
@@ -239,10 +356,15 @@ MultiSelectFilter.propTypes = {
239
356
  * If provided, sets a maximum number of items a user may select at once.
240
357
  */
241
358
  maxValues: PropTypes.number,
359
+ /**
360
+ * If provided sets maxHeight to be active
361
+ */
362
+ maxHeight: PropTypes.bool,
242
363
  /**
243
364
  * If provided, this function is called when the current selection is changed
244
365
  * and is passed an array of the `id`s of all currently selected `items`.
245
366
  */
367
+
246
368
  onChange: PropTypes.func,
247
369
  /**
248
370
  * Select English or French copy for the accessible label.
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useState } from 'react'
1
+ import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
4
  import { Image, Platform, Text, View } from 'react-native'
@@ -11,7 +11,7 @@ import {
11
11
  selectSystemProps
12
12
  } from '../utils'
13
13
  import { useViewport } from '../ViewportProvider'
14
- import { useThemeTokensCallback } from '../ThemeProvider'
14
+ import { applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
15
15
  import { Link } from '../Link'
16
16
  import { StackWrap } from '../StackView'
17
17
 
@@ -29,6 +29,7 @@ const selectImageStyle = (imageDimension) => ({
29
29
 
30
30
  const selectContainerStyle = ({ contentMaxDimension, textAlign }) => ({
31
31
  textAlign,
32
+ outline: 'red',
32
33
  width: contentMaxDimension,
33
34
  overflow: 'hidden'
34
35
  })
@@ -40,6 +41,8 @@ const selectImageContainerStyle = (contentMaxDimension) => ({
40
41
  alignItems: 'center'
41
42
  })
42
43
 
44
+ const selectLinkToken = ({ outerBorderColor }) => ({ outerBorderColor })
45
+
43
46
  /**
44
47
  * Component export along with QuickLinksFeature to be used as children
45
48
  *
@@ -49,42 +52,46 @@ const QuickLinksFeatureItem = forwardRef(
49
52
  ({ tokens, variant, children, imageAccessibilityLabel, imageSource, ...rest }, ref) => {
50
53
  const viewport = useViewport()
51
54
  const getTokens = useThemeTokensCallback('QuickLinksFeatureItem', tokens, variant)
52
- const [hover, setHover] = useState(false)
53
- const {
54
- contentDirection,
55
- contentSpace,
56
- contentAlignItems,
57
- contentMaxDimension,
58
- imageDimension,
59
- textAlign
60
- } = getTokens({ viewport, hover })
61
55
 
62
56
  return (
63
- <Link
64
- ref={ref}
65
- tokens={(state) => {
66
- setHover(state.hover)
67
- return getTokens(state)
68
- }}
69
- {...selectProps(rest)}
70
- >
71
- <View style={selectContainerStyle({ contentMaxDimension, textAlign })}>
72
- <StackWrap
73
- direction={contentDirection}
74
- space={contentSpace}
75
- tokens={{ alignItems: contentAlignItems }}
76
- >
77
- <View style={selectImageContainerStyle(contentMaxDimension)}>
78
- <Image
79
- accessibilityIgnoresInvertColors
80
- imageAccessibilityLabel={imageAccessibilityLabel}
81
- source={imageSource}
82
- style={selectImageStyle(imageDimension)}
83
- />
57
+ <Link ref={ref} {...selectProps(rest)} tokens={(state) => selectLinkToken(getTokens(state))}>
58
+ {({ hovered: hover, pressed, focused: focus }) => {
59
+ const {
60
+ contentDirection,
61
+ contentSpace,
62
+ contentAlignItems,
63
+ contentMaxDimension,
64
+ imageDimension,
65
+ textLine,
66
+ gap,
67
+ ...themeTokens
68
+ } = getTokens({ viewport, hover, pressed, focus })
69
+
70
+ const textStyle = {
71
+ ...applyTextStyles(themeTokens),
72
+ textDecorationLine: textLine
73
+ }
74
+ return (
75
+ <View style={selectContainerStyle({ ...themeTokens, contentMaxDimension })}>
76
+ <StackWrap
77
+ direction={contentDirection}
78
+ space={contentSpace}
79
+ gap={gap}
80
+ tokens={{ alignItems: contentAlignItems }}
81
+ >
82
+ <View style={selectImageContainerStyle(contentMaxDimension)}>
83
+ <Image
84
+ accessibilityIgnoresInvertColors
85
+ imageAccessibilityLabel={imageAccessibilityLabel}
86
+ source={imageSource}
87
+ style={selectImageStyle(imageDimension)}
88
+ />
89
+ </View>
90
+ <Text style={textStyle}>{children}</Text>
91
+ </StackWrap>
84
92
  </View>
85
- <Text>{children}</Text>
86
- </StackWrap>
87
- </View>
93
+ )
94
+ }}
88
95
  </Link>
89
96
  )
90
97
  }
@@ -59,17 +59,18 @@ const selectKnobStyles = (
59
59
  height: knobSize,
60
60
  width: knobSize,
61
61
  ...(isCompleted && {
62
- backgroundColor: knobCompletedBackgroundColor,
63
- borderColor: knobCompletedBorderColor,
64
- paddingLeft: knobCompletedPaddingLeft,
65
- paddingTop: knobCompletedPaddingTop
66
- }),
67
- ...(isCurrent && {
68
62
  backgroundColor: knobCurrentBackgroundColor,
69
63
  borderColor: knobCurrentBorderColor,
70
64
  borderWidth: knobCurrentBorderWidth,
71
65
  paddingLeft: knobCurrentPaddingLeft,
72
66
  paddingTop: knobCurrentPaddingTop
67
+ }),
68
+ ...(isCurrent && {
69
+ backgroundColor: knobCompletedBackgroundColor,
70
+ borderColor: knobCompletedBorderColor,
71
+ borderWidth: knobCurrentBorderWidth,
72
+ paddingLeft: knobCompletedPaddingLeft,
73
+ paddingTop: knobCompletedPaddingTop
73
74
  })
74
75
  })
75
76
  const selectLabelContainerStyles = ({
@@ -85,6 +86,26 @@ const selectLabelContainerStyles = ({
85
86
  flexDirection: labelDirection,
86
87
  gap: labelGap
87
88
  })
89
+ const selectStepLabelStyles = (
90
+ {
91
+ stepLabelColor,
92
+ labelCurrentColor,
93
+ stepLabelFontWeight,
94
+ stepLabelFontSize,
95
+ stepLabelFontName,
96
+ stepLabelLineHeight
97
+ },
98
+ themeOptions,
99
+ isCurrent
100
+ ) =>
101
+ applyTextStyles({
102
+ color: isCurrent ? labelCurrentColor : stepLabelColor,
103
+ fontSize: stepLabelFontSize,
104
+ lineHeight: stepLabelLineHeight,
105
+ fontWeight: stepLabelFontWeight,
106
+ fontName: stepLabelFontName,
107
+ themeOptions
108
+ })
88
109
  const selectLabelStyles = (
89
110
  {
90
111
  labelColor,
@@ -121,7 +142,8 @@ const getStepTestID = (isCompleted, isCurrent) => {
121
142
  * A single step of a StepTracker.
122
143
  */
123
144
  const Step = ({ label, name, status = 0, stepCount = 0, stepIndex = 0, tokens, ...rest }) => {
124
- const { completedIcon, showStepLabel, showStepName, ...themeTokens } = tokens
145
+ const { completedIcon, showStepLabel, showStepName, textStepTrackerLabel, ...themeTokens } =
146
+ tokens
125
147
  const isFirst = stepIndex === 0
126
148
  const isLast = stepIndex === stepCount - 1
127
149
  const isCompleted = status > stepIndex
@@ -150,10 +172,12 @@ const Step = ({ label, name, status = 0, stepCount = 0, stepIndex = 0, tokens, .
150
172
  style={[staticStyles.knob, selectKnobStyles(themeTokens, isCompleted, isCurrent)]}
151
173
  testID={getStepTestID(isCompleted, isCurrent)}
152
174
  >
153
- {isCompleted && completedIcon && (
175
+ {((isCompleted && completedIcon) || (isCurrent && !completedIcon)) && (
176
+ <View style={selectCurrentInnerStyles(themeTokens)} />
177
+ )}
178
+ {isCurrent && completedIcon && (
154
179
  <Icon icon={completedIcon} tokens={selectCompletedIconTokens(themeTokens)} />
155
180
  )}
156
- {isCurrent && <View style={selectCurrentInnerStyles(themeTokens)} />}
157
181
  </View>
158
182
  <View
159
183
  style={[
@@ -168,7 +192,7 @@ const Step = ({ label, name, status = 0, stepCount = 0, stepIndex = 0, tokens, .
168
192
  <Text
169
193
  style={[
170
194
  staticStyles.centeredText,
171
- selectLabelStyles(tokens, themeOptions, isCurrent)
195
+ selectStepLabelStyles(tokens, themeOptions, isCurrent)
172
196
  ]}
173
197
  >
174
198
  {name}
@@ -92,9 +92,13 @@ const StepTracker = forwardRef(
92
92
  viewport
93
93
  }
94
94
  )
95
+ const { textStepTrackerLabel } = themeTokens
95
96
  const getCopy = useCopy({ dictionary, copy })
96
97
  const stepTrackerLabel = showStepTrackerLabel
97
- ? getCopy('stepTrackerLabel')
98
+ ? (typeof copy === 'string'
99
+ ? getCopy(textStepTrackerLabel ?? 1).stepTrackerLabel
100
+ : getCopy('stepTrackerLabel')
101
+ )
98
102
  .replace('%{stepNumber}', current < steps.length ? current + 1 : steps.length)
99
103
  .replace('%{stepCount}', steps.length)
100
104
  .replace(
@@ -103,7 +107,12 @@ const StepTracker = forwardRef(
103
107
  )
104
108
  : ''
105
109
  const getStepLabel = (index) =>
106
- themeTokens.showStepLabel ? getCopy('stepLabel').replace('%{stepNumber}', index + 1) : ''
110
+ themeTokens.showStepLabel
111
+ ? (typeof copy === 'string'
112
+ ? getCopy(textStepTrackerLabel ?? 1).stepLabel
113
+ : getCopy('stepLabel')
114
+ ).replace('%{stepNumber}', index + 1)
115
+ : ''
107
116
  const { themeOptions } = useTheme()
108
117
  if (!steps.length) return null
109
118
  const selectedProps = selectProps({
@@ -1,10 +1,30 @@
1
1
  export default {
2
2
  en: {
3
- stepLabel: 'Step %{stepNumber}',
4
- stepTrackerLabel: 'Step %{stepNumber} of %{stepCount}: %{stepLabel}'
3
+ 1: {
4
+ stepLabel: 'Step %{stepNumber}',
5
+ stepTrackerLabel: 'Step %{stepNumber} of %{stepCount}: %{stepLabel}'
6
+ },
7
+ 2: {
8
+ stepLabel: '%{stepNumber}.',
9
+ stepTrackerLabel: 'Step %{stepNumber} of %{stepCount}: %{stepLabel}'
10
+ },
11
+ 3: {
12
+ stepLabel: 'Step %{stepNumber}',
13
+ stepTrackerLabel: 'Step %{stepNumber} of %{stepCount}: %{stepLabel}'
14
+ }
5
15
  },
6
16
  fr: {
7
- stepLabel: 'Étape %{stepNumber}',
8
- stepTrackerLabel: 'Étape %{stepNumber} sur %{stepCount}: %{stepLabel}'
17
+ 1: {
18
+ stepLabel: 'Étape %{stepNumber}',
19
+ stepTrackerLabel: 'Étape %{stepNumber} sur %{stepCount}: %{stepLabel}'
20
+ },
21
+ 2: {
22
+ stepLabel: '%{stepNumber}.',
23
+ stepTrackerLabel: 'Étape %{stepNumber} sur %{stepCount}: %{stepLabel}'
24
+ },
25
+ 3: {
26
+ stepLabel: 'Étape %{stepNumber}',
27
+ stepTrackerLabel: 'Étape %{stepNumber} sur %{stepCount}: %{stepLabel}'
28
+ }
9
29
  }
10
30
  }
@@ -105,6 +105,7 @@ const Typography = forwardRef(
105
105
  const textStyles = resolvedTextProps.style
106
106
  ? { ...resolvedTextProps.style, textDecorationLine }
107
107
  : { textDecorationLine }
108
+
108
109
  return block ? (
109
110
  <View ref={ref} {...containerProps}>
110
111
  <Text {...resolvedTextProps} style={textStyles}>