@telus-uds/components-web 2.13.0 → 2.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.
@@ -42,7 +42,8 @@ const Badge = ({ children, tokens, variant = {}, ...rest }) => {
42
42
  paddingTop,
43
43
  paddingBottom,
44
44
  fontName,
45
- fontWeight
45
+ fontWeight,
46
+ fontSize
46
47
  } = useThemeTokens('Badge', tokens, variant)
47
48
 
48
49
  const semanticGradient = gradient && transformGradient(gradient)
@@ -56,6 +57,12 @@ const Badge = ({ children, tokens, variant = {}, ...rest }) => {
56
57
  background = semanticGradient
57
58
  }
58
59
 
60
+ const fontSizeMapping = {
61
+ 12: 'micro',
62
+ 14: 'small',
63
+ 16: 'h6'
64
+ }
65
+
59
66
  return (
60
67
  <BadgeContainer
61
68
  isOutlineOffer={isOutlineOffer}
@@ -69,7 +76,12 @@ const Badge = ({ children, tokens, variant = {}, ...rest }) => {
69
76
  border={`${borderWidth}px solid ${borderColor}`}
70
77
  {...selectProps(rest)}
71
78
  >
72
- <Typography tokens={{ fontName, fontWeight, color }}>{children}</Typography>
79
+ <Typography
80
+ tokens={{ fontName, fontWeight, color }}
81
+ variant={{ size: fontSizeMapping[fontSize] }}
82
+ >
83
+ {children}
84
+ </Typography>
73
85
  </BadgeContainer>
74
86
  )
75
87
  }
@@ -15,6 +15,7 @@ import {
15
15
  applyTextStyles
16
16
  } from '@telus-uds/components-base'
17
17
  import moment from 'moment'
18
+ import { isUndefined } from 'lodash'
18
19
  import CalendarContainer from './CalendarContainer'
19
20
  import dictionary from './dictionary'
20
21
  import { htmlAttrs } from '../utils'
@@ -106,7 +107,11 @@ const DatePicker = forwardRef(
106
107
  const [isClickedInside, setIsClickedInside] = useState(false)
107
108
  const getCopy = useCopy({ dictionary, copy })
108
109
  useEffect(() => {
109
- if (!moment(date).isSame(inputDate)) {
110
+ /**
111
+ * `date` could be passed as `null` to reset the value so explicitly
112
+ * checking for not being `undefined`
113
+ */
114
+ if (!isUndefined(date) && !moment(date).isSame(inputDate)) {
110
115
  setInputDate(date)
111
116
  setInputText(date instanceof moment ? date.format(dateFormat) : '')
112
117
  }
@@ -139,6 +144,7 @@ const DatePicker = forwardRef(
139
144
  const onChange = (value) => {
140
145
  setInputDate(value)
141
146
  setInputText(value.format(dateFormat))
147
+ setIsFocused(false)
142
148
  if (onDateChange) onDateChange(value)
143
149
  }
144
150
  const onChangeInput = (value) => {
@@ -1,7 +1,6 @@
1
1
  import React, { useState } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import {
4
- getThemeTokens,
5
4
  Box,
6
5
  TextInput,
7
6
  Spacer,
@@ -9,13 +8,12 @@ import {
9
8
  InputLabel,
10
9
  useInputValue,
11
10
  useCopy,
12
- useThemeTokens,
13
- useTheme
11
+ useThemeTokensCallback
14
12
  } from '@telus-uds/components-base'
15
13
 
16
- import IconButton from '../IconButton'
17
- import { InputField, InputWrapper, LeftButtonWrapper, RightButtonWrapper } from './styles'
14
+ import { InputField, InputWrapper } from './styles'
18
15
  import defaultDictionary from './dictionary'
16
+ import SideButton from './SideButton'
19
17
 
20
18
  const { isNaN } = Number
21
19
 
@@ -28,6 +26,7 @@ const QuantitySelector = ({
28
26
  minNumber,
29
27
  maxNumber,
30
28
  defaultValue,
29
+ value,
31
30
  label,
32
31
  hint,
33
32
  hintPosition,
@@ -40,13 +39,8 @@ const QuantitySelector = ({
40
39
  tokens,
41
40
  testID
42
41
  }) => {
43
- const {
44
- components: { QuantitySelector: componentTheme }
45
- } = useTheme()
46
-
47
- const { leftIcon, rightIcon, padding } = useThemeTokens('QuantitySelector', tokens, variant)
42
+ const { disabled } = variant
48
43
  const [error, setError] = useState('')
49
- const { alternative } = variant
50
44
  const getCopy = useCopy({ dictionary, copy })
51
45
 
52
46
  const getValidatedNumber = (numberToEvaluate) => {
@@ -58,16 +52,14 @@ const QuantitySelector = ({
58
52
  return numberToEvaluate
59
53
  }
60
54
 
61
- const initialValue = getValidatedNumber(defaultValue)
62
-
63
55
  const { currentValue: number, setValue: setNumber } = useInputValue({
64
- value: undefined,
65
- initialValue,
56
+ value: getValidatedNumber(value),
57
+ initialValue: getValidatedNumber(defaultValue),
66
58
  onChange
67
59
  })
68
60
 
69
- const isDecreaseEnabled = !isNumber(minNumber) || number > minNumber
70
- const isIncreaseEnabled = !isNumber(maxNumber) || number < maxNumber
61
+ const isDecreaseEnabled = (!disabled && !isNumber(minNumber)) || number > minNumber
62
+ const isIncreaseEnabled = (!disabled && !isNumber(maxNumber)) || number < maxNumber
71
63
  const inputValue = isNumber(number) ? number.toString() : ''
72
64
 
73
65
  const updateNumber = (newNumber, originalInputEvent) => {
@@ -101,24 +93,34 @@ const QuantitySelector = ({
101
93
  />
102
94
  ) : null
103
95
 
96
+ const getTokens = useThemeTokensCallback('QuantitySelector', tokens, variant)
97
+
104
98
  const renderTextInput = () => (
105
99
  <TextInput
106
100
  nativeID={id}
107
101
  value={inputValue}
102
+ defaultValue={defaultValue}
108
103
  tokens={(textInputState) => {
109
- const { inputBorderColor, inputBackgroundColor } = getThemeTokens(
110
- componentTheme,
111
- tokens,
112
- variant,
113
- textInputState
114
- )
115
-
104
+ const {
105
+ inputWidth,
106
+ inputBorderWidth,
107
+ inputBorderColor,
108
+ textColor,
109
+ inputBackgroundColor,
110
+ ...rest
111
+ } = getTokens({
112
+ ...textInputState
113
+ })
116
114
  return {
117
- borderRadius: 0,
118
- margin: 0,
119
- padding: 0,
115
+ ...rest,
116
+ order: 1,
117
+ borderWidth: inputBorderWidth,
118
+ backgroundColor: inputBackgroundColor,
119
+ color: textColor,
120
+ width: inputWidth,
120
121
  borderColor: inputBorderColor,
121
- backgroundColor: inputBackgroundColor
122
+ borderRadius: 0,
123
+ outerBorderWidth: 0
122
124
  }
123
125
  }}
124
126
  onChange={inputChangeHandler}
@@ -133,58 +135,34 @@ const QuantitySelector = ({
133
135
  />
134
136
  )
135
137
 
136
- const getButtonTokens = (isEnabled) => (buttonState) => {
137
- const disabled = !isEnabled
138
- const { ...buttonTokens } = getThemeTokens(componentTheme, tokens, variant, {
139
- ...buttonState,
140
- disabled
141
- })
142
-
143
- return {
144
- ...buttonTokens,
145
- outerBorderGap: 0,
146
- padding
147
- }
148
- }
149
-
150
- const renderLeftButton = () => {
151
- return (
152
- <IconButton
153
- icon={leftIcon}
154
- tokens={getButtonTokens(isDecreaseEnabled)}
155
- onPress={(event) => updateNumber(number - 1, event)}
156
- onDoubleClick={(event) => updateNumber(number - 1, event)}
157
- accessibilityLabel={getCopy('accessibility').decreaseButton}
158
- accessibilityDisabled={!isDecreaseEnabled}
159
- />
160
- )
161
- }
162
-
163
- const renderRightButton = () => (
164
- <IconButton
165
- icon={rightIcon}
166
- tokens={getButtonTokens(isIncreaseEnabled)}
167
- onPress={() => updateNumber(number + 1)}
168
- onDoubleClick={() => updateNumber(number + 1)}
169
- accessibilityLabel={getCopy('accessibility').increaseButton}
170
- accessibilityDisabled={!isIncreaseEnabled}
171
- />
172
- )
173
-
174
138
  return (
175
139
  <Box space={2} testID={testID}>
176
140
  {renderLabel()}
177
141
  <Spacer space={2} />
178
142
  <InputWrapper>
179
- <InputField className={`${alternative ? 'alternative' : ''}`}>
180
- {renderTextInput()}
181
- </InputField>
182
- <RightButtonWrapper className={`${alternative ? 'alternative' : ''}`}>
183
- {renderRightButton()}
184
- </RightButtonWrapper>
185
- <LeftButtonWrapper className={`${alternative ? 'alternative' : ''}`}>
186
- {renderLeftButton()}
187
- </LeftButtonWrapper>
143
+ <InputField>{renderTextInput()}</InputField>
144
+ <div style={{ order: 0 }}>
145
+ <SideButton
146
+ isEnabled={isDecreaseEnabled}
147
+ onPress={() => updateNumber(number - 1)}
148
+ onDoubleClick={() => updateNumber(number - 1)}
149
+ tokens={tokens}
150
+ variant={{ decrease: true, ...variant }}
151
+ accessibilityLabel={getCopy('accessibility').decreaseButton}
152
+ accessibilityDisabled={!isDecreaseEnabled}
153
+ />
154
+ </div>
155
+ <div style={{ order: 2 }}>
156
+ <SideButton
157
+ isEnabled={isIncreaseEnabled}
158
+ onPress={() => updateNumber(number + 1)}
159
+ onDoubleClick={() => updateNumber(number + 1)}
160
+ accessibilityLabel={getCopy('accessibility').increaseButton}
161
+ accessibilityDisabled={!isIncreaseEnabled}
162
+ tokens={tokens}
163
+ variant={{ increase: true, ...variant }}
164
+ />
165
+ </div>
188
166
  </InputWrapper>
189
167
  {error ? (
190
168
  <Box vertical={2}>
@@ -230,6 +208,11 @@ QuantitySelector.propTypes = {
230
208
  * The default value of the input field
231
209
  */
232
210
  defaultValue: PropTypes.number,
211
+ /**
212
+ * If the input's state is to be controlled by a parent component, use this prop
213
+ * together with the `onChange` to pass down and update the lifted state.
214
+ */
215
+ value: PropTypes.number,
233
216
  /**
234
217
  * The label of the input field
235
218
  */
@@ -262,7 +245,8 @@ QuantitySelector.propTypes = {
262
245
  */
263
246
  copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr'])]),
264
247
  variant: PropTypes.exact({
265
- alternative: PropTypes.bool
248
+ alternative: PropTypes.bool,
249
+ disabled: PropTypes.bool
266
250
  }),
267
251
  tokens: PropTypes.oneOf([PropTypes.object, PropTypes.func]),
268
252
  /**
@@ -0,0 +1,74 @@
1
+ import { IconButton, useThemeTokensCallback } from '@telus-uds/components-base'
2
+ import React from 'react'
3
+ import PropTypes from 'prop-types'
4
+
5
+ const SideButton = ({
6
+ isEnabled,
7
+ onPress,
8
+ onDoubleClick,
9
+ accessibilityLabel,
10
+ accessibilityDisabled,
11
+ tokens,
12
+ variant
13
+ }) => {
14
+ const getTokens = useThemeTokensCallback('QuantitySelectorSideButton', tokens, variant)
15
+ const getButtonTokens = ({ buttonState, disabled }) => {
16
+ const {
17
+ borderRadius,
18
+ borderTopLeftRadius,
19
+ borderTopRightRadius,
20
+ borderBottomLeftRadius,
21
+ borderBottomRightRadius,
22
+ ...rest
23
+ } = getTokens({ ...buttonState, disabled })
24
+
25
+ return {
26
+ ...rest,
27
+ borderRadius,
28
+ borderTopLeftRadius,
29
+ borderTopRightRadius,
30
+ borderBottomLeftRadius,
31
+ borderBottomRightRadius,
32
+ outerBorderRadius: borderRadius,
33
+ outerBorderTopLeftRadius: borderTopLeftRadius,
34
+ outerBorderTopRightRadius: borderTopRightRadius,
35
+ outerBorderBottomLeftRadius: borderBottomLeftRadius,
36
+ outerBorderBottomRightRadius: borderBottomRightRadius,
37
+ outerBorderGap: 0,
38
+ outerBorderWidth: 0
39
+ }
40
+ }
41
+
42
+ return (
43
+ <IconButton
44
+ tokens={(buttonState) => getButtonTokens({ disabled: !isEnabled, buttonState })}
45
+ onPress={onPress}
46
+ onDoubleClick={onDoubleClick}
47
+ accessibilityLabel={accessibilityLabel}
48
+ accessibilityDisabled={accessibilityDisabled}
49
+ />
50
+ )
51
+ }
52
+
53
+ SideButton.displayName = 'QuantitySelectorSideButton'
54
+
55
+ SideButton.defaultProps = {
56
+ isEnabled: true,
57
+ onPress: () => {},
58
+ onDoubleClick: () => {},
59
+ accessibilityLabel: '',
60
+ accessibilityDisabled: false,
61
+ tokens: {},
62
+ variant: {}
63
+ }
64
+
65
+ SideButton.propTypes = {
66
+ isEnabled: PropTypes.bool,
67
+ onPress: PropTypes.func,
68
+ onDoubleClick: PropTypes.func,
69
+ accessibilityLabel: PropTypes.string,
70
+ accessibilityDisabled: PropTypes.bool,
71
+ tokens: PropTypes.object,
72
+ variant: PropTypes.object
73
+ }
74
+ export default SideButton
@@ -1,83 +1,17 @@
1
1
  import styled from 'styled-components'
2
2
 
3
3
  export const InputField = styled.div`
4
- order: 2;
5
- min-width: 3.5rem;
6
- max-width: 5 rem;
7
- height: 28px;
8
- padding: 0;
9
- width: 64px;
4
+ order: 1;
10
5
  text-align: center;
11
6
  z-index: 10;
12
7
  input {
13
8
  text-align: center;
14
9
  }
15
-
16
- &.alternative {
17
- input {
18
- height: 40px;
19
- }
20
- }
21
- `
22
-
23
- const ButtonWrapper = styled.div`
24
- &.alternative {
25
- div[role='button'] {
26
- height: 42px;
27
- > div {
28
- height: 40px;
29
- > div {
30
- padding: 12px 16px;
31
- }
32
- }
33
- }
34
- }
35
- `
36
-
37
- export const LeftButtonWrapper = styled(ButtonWrapper)`
38
- order: 0;
39
- div[role='button'] {
40
- border-radius: 4px 0px 0px 4px !important;
41
- > div {
42
- border-right: none;
43
- border-radius: 4px 0px 0px 4px !important;
44
- }
45
- }
46
-
47
- &.alternative {
48
- div[role='button'] {
49
- border-radius: 36px 0px 0px 36px !important;
50
- > div {
51
- border-radius: 24px 0px 0px 24px !important;
52
- }
53
- }
54
- }
55
- `
56
-
57
- export const RightButtonWrapper = styled(ButtonWrapper)`
58
- order: 3;
59
- div[role='button'] {
60
- border-radius: 0px 4px 4px 0px !important;
61
- > div {
62
- border-left: none;
63
- border-radius: 0px 4px 4px 0px !important;
64
- }
65
- }
66
-
67
- &.alternative {
68
- div[role='button'] {
69
- border-radius: 0px 36px 36px 0px !important;
70
- > div {
71
- border-radius: 0px 36px 36px 0px !important;
72
- }
73
- }
74
- }
75
10
  `
76
11
 
77
12
  export const InputWrapper = styled.div`
78
- textalign: start;
13
+ text-align: start;
79
14
  display: flex;
80
- flexdirection: row;
81
- flexwrap: nowrap;
82
- justifycontent: center;
15
+ flex-direction: row;
16
+ flex-wrap: nowrap;
83
17
  `
@@ -13,7 +13,7 @@ import {
13
13
  import ExpandCollapse from './ExpandCollapse'
14
14
  import OrderedListBase from '../OrderedList/OrderedListBase'
15
15
  import { htmlAttrs, media, renderStructuredContent } from '../utils'
16
- import dictionary from './dictionary'
16
+ import defaultDictionary from './dictionary'
17
17
 
18
18
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
19
19
 
@@ -72,7 +72,18 @@ const NonIndexedContentTitle = styled.div(({ tokens }) => ({
72
72
  * - Use `copy` to set language, ‘en’ for English or ‘fr’ for French
73
73
  */
74
74
  const TermsAndConditions = forwardRef(
75
- ({ copy = 'en', indexedContent, nonIndexedContent, tokens, variant = {}, ...rest }, ref) => {
75
+ (
76
+ {
77
+ copy = 'en',
78
+ indexedContent,
79
+ nonIndexedContent,
80
+ tokens,
81
+ variant = {},
82
+ dictionary = defaultDictionary,
83
+ ...rest
84
+ },
85
+ ref
86
+ ) => {
76
87
  const getCopy = useCopy({ dictionary, copy })
77
88
  const hasIndexedContent = indexedContent.length > 0
78
89
  const hasNonIndexedContent = nonIndexedContent.length > 0
@@ -135,6 +146,13 @@ const TermsAndConditions = forwardRef(
135
146
 
136
147
  TermsAndConditions.displayName = 'TermsAndConditions'
137
148
 
149
+ // If a language dictionary entry is provided, it must contain every key
150
+ const dictionaryContentShape = PropTypes.shape({
151
+ headingHide: PropTypes.string.isRequired,
152
+ headingView: PropTypes.string.isRequired,
153
+ nonIndexedTitle: PropTypes.string.isRequired
154
+ })
155
+
138
156
  TermsAndConditions.propTypes = {
139
157
  ...selectedSystemPropTypes,
140
158
  /**
@@ -161,13 +179,21 @@ TermsAndConditions.propTypes = {
161
179
  *
162
180
  * nonIndexedContent do not have a corresponding superscript and instead apply to the page as a whole.
163
181
  */
164
- nonIndexedContent: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.node, PropTypes.string]))
182
+ nonIndexedContent: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.node, PropTypes.string])),
183
+ /**
184
+ * Custom dictionary containing the labels to use for `TermsAndConditions`
185
+ */
186
+ dictionary: PropTypes.shape({
187
+ en: dictionaryContentShape,
188
+ fr: dictionaryContentShape
189
+ })
165
190
  }
166
191
 
167
192
  TermsAndConditions.defaultProps = {
168
193
  copy: 'en',
169
194
  indexedContent: [],
170
- nonIndexedContent: []
195
+ nonIndexedContent: [],
196
+ dictionary: defaultDictionary
171
197
  }
172
198
 
173
199
  export default TermsAndConditions
@@ -1,7 +1,13 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import styled from 'styled-components'
4
- import { Icon, selectSystemProps, Typography, useThemeTokens } from '@telus-uds/components-base'
4
+ import {
5
+ Icon,
6
+ selectSystemProps,
7
+ Typography,
8
+ useThemeTokens,
9
+ useViewport
10
+ } from '@telus-uds/components-base'
5
11
  import Image from '../Image'
6
12
  import { htmlAttrs } from '../utils'
7
13
 
@@ -48,11 +54,13 @@ const Testimonial = ({
48
54
  imageSrc,
49
55
  image = imageSrc,
50
56
  additionalInfo,
51
- testimonialStyle = 'heading',
57
+ testimonialStyle = 'large',
52
58
  tokens,
59
+ copy = 'en',
53
60
  variant = {},
54
61
  ...rest
55
62
  }) => {
63
+ const viewport = useViewport()
56
64
  const {
57
65
  testimonialContainerGap,
58
66
  quoteContainerGap,
@@ -61,13 +69,43 @@ const Testimonial = ({
61
69
  figcaptionGap,
62
70
  textColor,
63
71
  icon,
72
+ iconFr,
64
73
  iconColor,
65
- imageSize
66
- } = useThemeTokens('Testimonial', tokens, variant)
74
+ imageSize,
75
+ testimonialFontSizeLarge,
76
+ testimonialLineHeightLarge,
77
+ testimonialFontWeightLarge,
78
+ testimonialFontSizeHeading,
79
+ testimonialLineHeightHeading,
80
+ testimonialFontNameHeading,
81
+ testimonialFontWeightHeading,
82
+ authorFontSize,
83
+ authorLineHeight,
84
+ authorFontName,
85
+ authorFontWeight,
86
+ additionalFontSize,
87
+ additionalLineHeight,
88
+ additionalFontName,
89
+ additionalFontWeight
90
+ } = useThemeTokens('Testimonial', tokens, variant, { viewport })
91
+
92
+ const getQuoteTestimonial = (open) => {
93
+ let quote = ''
94
+
95
+ if (copy === 'en') quote = open ? '\u201C' : '\u201D'
96
+ else quote = open ? '\u00AB ' : ' \u00BB'
97
+
98
+ return quote
99
+ }
100
+
67
101
  return (
68
102
  <TestimonialContainer testimonialContainerGap={testimonialContainerGap} {...selectProps(rest)}>
69
103
  <QuoteContainer quoteContainerGap={quoteContainerGap}>
70
- <Icon tokens={{ color: iconColor }} variant={{ size: 'micro' }} icon={icon} />
104
+ <Icon
105
+ tokens={{ color: iconColor }}
106
+ variant={{ size: 'micro' }}
107
+ icon={copy === 'en' ? icon : iconFr}
108
+ />
71
109
  {showDivider && (
72
110
  <Divider
73
111
  dividerBackgroundColor={dividerBackgroundColor}
@@ -81,10 +119,22 @@ const Testimonial = ({
81
119
  variant={{ size: testimonialStyle === 'large' ? 'large' : 'h3' }}
82
120
  tokens={{
83
121
  color: textColor,
84
- fontWeight: '400'
122
+ fontSize:
123
+ testimonialStyle === 'heading'
124
+ ? testimonialFontSizeHeading
125
+ : testimonialFontSizeLarge,
126
+ lineHeight:
127
+ testimonialStyle === 'heading'
128
+ ? testimonialLineHeightHeading
129
+ : testimonialLineHeightLarge,
130
+ fontName: testimonialFontNameHeading,
131
+ fontWeight:
132
+ testimonialStyle === 'heading'
133
+ ? testimonialFontWeightHeading
134
+ : testimonialFontWeightLarge
85
135
  }}
86
136
  >
87
- {`\u201C${testimonial}\u201D`}
137
+ {`${getQuoteTestimonial(true)}${testimonial}${getQuoteTestimonial()}`}
88
138
  </Typography>
89
139
  </BlockQuote>
90
140
  {(image || title || additionalInfo) && (
@@ -105,16 +155,28 @@ const Testimonial = ({
105
155
  <AuthorInfoContainer>
106
156
  {title && (
107
157
  <Typography
108
- variant={{ size: 'small', colour: 'secondary' }}
109
- tokens={{ fontWeight: '500' }}
158
+ variant={{ size: 'small' }}
159
+ tokens={{
160
+ color: textColor,
161
+ fontSize: authorFontSize,
162
+ lineHeight: authorLineHeight,
163
+ fontName: authorFontName,
164
+ fontWeight: authorFontWeight
165
+ }}
110
166
  >
111
167
  {title}
112
168
  </Typography>
113
169
  )}
114
170
  {additionalInfo && (
115
171
  <Typography
116
- variant={{ size: 'micro', colour: 'secondary' }}
117
- tokens={{ fontWeight: '400' }}
172
+ variant={{ size: 'small' }}
173
+ tokens={{
174
+ color: textColor,
175
+ fontSize: additionalFontSize,
176
+ lineHeight: additionalLineHeight,
177
+ fontName: additionalFontName,
178
+ fontWeight: additionalFontWeight
179
+ }}
118
180
  >
119
181
  {additionalInfo}
120
182
  </Typography>