@telus-uds/components-web 2.12.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +40 -2
  2. package/component-docs.json +105 -43
  3. package/lib/Autocomplete/Loading.js +5 -10
  4. package/lib/Autocomplete/dictionary.js +2 -2
  5. package/lib/Badge/Badge.js +10 -1
  6. package/lib/DatePicker/DatePicker.js +13 -0
  7. package/lib/NavigationBar/NavigationSubMenu.js +4 -8
  8. package/lib/QuantitySelector/QuantitySelector.js +67 -66
  9. package/lib/QuantitySelector/SideButton.js +93 -0
  10. package/lib/QuantitySelector/styles.js +4 -20
  11. package/lib/Spinner/Spinner.js +10 -1
  12. package/lib/Spinner/SpinnerContent.js +8 -0
  13. package/lib/Table/Cell.js +62 -91
  14. package/lib/Table/Header.js +4 -1
  15. package/lib/Table/SubHeading.js +4 -1
  16. package/lib/Table/Table.js +2 -1
  17. package/lib/TermsAndConditions/ExpandCollapse.js +31 -13
  18. package/lib/TermsAndConditions/TermsAndConditions.js +42 -10
  19. package/lib/Testimonial/Testimonial.js +48 -12
  20. package/lib/VideoPicker/VideoPickerPlayer.js +4 -2
  21. package/lib/VideoPicker/VideoPickerThumbnail.js +103 -60
  22. package/lib/VideoPicker/VideoSlider.js +2 -2
  23. package/lib-module/Autocomplete/Loading.js +6 -12
  24. package/lib-module/Autocomplete/dictionary.js +2 -2
  25. package/lib-module/Badge/Badge.js +10 -1
  26. package/lib-module/DatePicker/DatePicker.js +13 -1
  27. package/lib-module/NavigationBar/NavigationSubMenu.js +5 -9
  28. package/lib-module/QuantitySelector/QuantitySelector.js +68 -67
  29. package/lib-module/QuantitySelector/SideButton.js +80 -0
  30. package/lib-module/QuantitySelector/styles.js +3 -15
  31. package/lib-module/Spinner/Spinner.js +10 -1
  32. package/lib-module/Spinner/SpinnerContent.js +8 -0
  33. package/lib-module/Table/Cell.js +65 -90
  34. package/lib-module/Table/Header.js +4 -1
  35. package/lib-module/Table/SubHeading.js +4 -1
  36. package/lib-module/Table/Table.js +2 -1
  37. package/lib-module/TermsAndConditions/ExpandCollapse.js +33 -15
  38. package/lib-module/TermsAndConditions/TermsAndConditions.js +42 -11
  39. package/lib-module/Testimonial/Testimonial.js +49 -13
  40. package/lib-module/VideoPicker/VideoPickerPlayer.js +4 -2
  41. package/lib-module/VideoPicker/VideoPickerThumbnail.js +103 -61
  42. package/lib-module/VideoPicker/VideoSlider.js +2 -2
  43. package/package.json +4 -4
  44. package/src/Autocomplete/Loading.jsx +2 -5
  45. package/src/Autocomplete/dictionary.js +2 -2
  46. package/src/Badge/Badge.jsx +14 -2
  47. package/src/DatePicker/DatePicker.jsx +14 -1
  48. package/src/NavigationBar/NavigationSubMenu.jsx +3 -4
  49. package/src/QuantitySelector/QuantitySelector.jsx +60 -76
  50. package/src/QuantitySelector/SideButton.jsx +74 -0
  51. package/src/QuantitySelector/styles.js +4 -70
  52. package/src/Spinner/Spinner.jsx +9 -1
  53. package/src/Spinner/SpinnerContent.jsx +13 -1
  54. package/src/Table/Cell.jsx +58 -78
  55. package/src/Table/Header.jsx +6 -1
  56. package/src/Table/SubHeading.jsx +4 -1
  57. package/src/Table/Table.jsx +10 -2
  58. package/src/TermsAndConditions/ExpandCollapse.jsx +36 -14
  59. package/src/TermsAndConditions/TermsAndConditions.jsx +48 -10
  60. package/src/Testimonial/Testimonial.jsx +73 -11
  61. package/src/VideoPicker/VideoPickerPlayer.jsx +2 -2
  62. package/src/VideoPicker/VideoPickerThumbnail.jsx +51 -30
  63. package/src/VideoPicker/VideoSlider.jsx +2 -2
  64. package/types/BaseProvider.d.ts +25 -0
  65. package/types/index.d.ts +1 -1
@@ -4,7 +4,7 @@ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
4
4
  import PropTypes from 'prop-types';
5
5
  import { viewports } from '@telus-uds/system-constants';
6
6
  import styled from 'styled-components';
7
- import { StackView, Typography, useViewport, horizontalScrollUtils, useThemeTokens } from '@telus-uds/components-base';
7
+ import { StackView, Typography, useViewport, horizontalScrollUtils, useThemeTokensCallback } from '@telus-uds/components-base';
8
8
  import { getTimestamp } from '../shared/VideoSplash/helpers';
9
9
  import { VideoPropType, RefPropType } from './videoPropType';
10
10
  import VideoSplash from '../shared/VideoSplash/VideoSplash';
@@ -42,37 +42,45 @@ const createReactNativeStyles = _ref => {
42
42
  });
43
43
  };
44
44
 
45
- const VideoThumbnail = /*#__PURE__*/styled.div.withConfig({
46
- displayName: "VideoPickerThumbnail__VideoThumbnail",
45
+ const ImageContainer = /*#__PURE__*/styled.div.withConfig({
46
+ displayName: "VideoPickerThumbnail__ImageContainer",
47
47
  componentId: "components-web__sc-1glxurq-0"
48
- })(["position:relative;width:", ";flex-shrink:0;* button{display:none;}&::before{content:'';display:block;padding-bottom:56.25%;}&::after{content:'';border:", "px solid;border-color:", ";border-radius:", "px;position:absolute;top:0;left:0;right:0;bottom:0;}& > div{border-radius:", "px;}"], props => props.layout === 'vertical' ? '100%' : '144px', _ref2 => {
48
+ })(["padding:", ";border:", ";border-radius:", "px;"], props => `${props.outerBorderGap}px`, props => `${props.outerBorderWidth}px solid ${props.outerBorderColor}`, _ref2 => {
49
49
  let {
50
- borderWidth
50
+ outerBorderRadius
51
51
  } = _ref2;
52
- return borderWidth;
53
- }, _ref3 => {
52
+ return outerBorderRadius;
53
+ });
54
+ const VideoThumbnail = /*#__PURE__*/styled.div.withConfig({
55
+ displayName: "VideoPickerThumbnail__VideoThumbnail",
56
+ componentId: "components-web__sc-1glxurq-1"
57
+ })(["position:relative;width:", ";flex-shrink:0;* button{display:none;}&::before{content:'';display:block;padding-bottom:56.25%;}&::after{content:'';border:", "px solid;border-color:", ";border-radius:", "px;position:absolute;top:0;left:0;right:0;bottom:0;}& > div{border-radius:", "px;}"], props => props.layout === 'vertical' ? '100%' : '144px', _ref3 => {
54
58
  let {
55
- isPlaying,
56
- borderColor
59
+ borderWidth
57
60
  } = _ref3;
58
- return isPlaying ? borderColor : 'transparent';
61
+ return borderWidth;
59
62
  }, _ref4 => {
60
63
  let {
61
- borderRadius
64
+ borderColor
62
65
  } = _ref4;
63
- return borderRadius;
66
+ return borderColor;
64
67
  }, _ref5 => {
65
68
  let {
66
69
  borderRadius
67
70
  } = _ref5;
68
71
  return borderRadius;
72
+ }, _ref6 => {
73
+ let {
74
+ borderRadius
75
+ } = _ref6;
76
+ return borderRadius;
69
77
  });
70
78
  const ThumbnailTitleContainer = /*#__PURE__*/styled.div.withConfig({
71
79
  displayName: "VideoPickerThumbnail__ThumbnailTitleContainer",
72
- componentId: "components-web__sc-1glxurq-1"
80
+ componentId: "components-web__sc-1glxurq-2"
73
81
  })(["display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;"]);
74
82
 
75
- const VideoPickerThumbnail = _ref6 => {
83
+ const VideoPickerThumbnail = _ref7 => {
76
84
  let {
77
85
  videoPlayerRef,
78
86
  selectedVideoId,
@@ -83,56 +91,59 @@ const VideoPickerThumbnail = _ref6 => {
83
91
  itemPositions,
84
92
  index,
85
93
  width = '100%'
86
- } = _ref6;
94
+ } = _ref7;
87
95
  const viewport = useViewport();
88
- const {
89
- titleColor,
90
- subTitleColor,
91
- ...themeTokens
92
- } = useThemeTokens('VideoPickerThumbnail');
93
- const rnStyles = createReactNativeStyles(themeTokens);
96
+ const getTokens = useThemeTokensCallback('VideoPickerThumbnail', {}, {});
94
97
  const {
95
98
  timestamp
96
99
  } = getTimestamp(video.videoLength, video.copy);
97
100
  const isPlaying = selectedVideoId === video.videoId;
98
101
 
99
- const renderThumbnailImage = () => /*#__PURE__*/_jsx(VideoThumbnail, { ...themeTokens,
100
- isPlaying: isPlaying,
101
- layout: layout,
102
- children: /*#__PURE__*/_jsx(VideoSplash, {
103
- simpleMode: true,
104
- poster: video.posterSrc || `https://img.youtube.com/vi/${video.videoId}/maxresdefault.jpg`,
105
- videoLength: video.videoLength,
106
- copy: video.copy
107
- })
108
- });
102
+ const renderThumbnailImage = themeTokens => {
103
+ return /*#__PURE__*/_jsx(VideoThumbnail, { ...themeTokens,
104
+ isPlaying: isPlaying,
105
+ layout: layout,
106
+ children: /*#__PURE__*/_jsx(VideoSplash, {
107
+ simpleMode: true,
108
+ poster: video.posterSrc || `https://img.youtube.com/vi/${video.videoId}/maxresdefault.jpg`,
109
+ videoLength: video.videoLength,
110
+ copy: video.copy
111
+ })
112
+ });
113
+ };
109
114
 
110
- const renderThumbnailInfo = () => /*#__PURE__*/_jsxs(StackView, {
111
- space: 2,
112
- tokens: {
113
- flexShrink: 1
114
- },
115
- children: [/*#__PURE__*/_jsx(ThumbnailTitleContainer, {
116
- viewport: viewport,
117
- children: /*#__PURE__*/_jsx(Typography, {
115
+ const renderThumbnailInfo = _ref8 => {
116
+ let {
117
+ titleColor,
118
+ subTitleColor
119
+ } = _ref8;
120
+ return /*#__PURE__*/_jsxs(StackView, {
121
+ space: 2,
122
+ tokens: {
123
+ flexShrink: 1
124
+ },
125
+ children: [/*#__PURE__*/_jsx(ThumbnailTitleContainer, {
126
+ viewport: viewport,
127
+ children: /*#__PURE__*/_jsx(Typography, {
128
+ variant: {
129
+ bold: true
130
+ },
131
+ tokens: {
132
+ color: titleColor
133
+ },
134
+ children: video.title
135
+ })
136
+ }), viewport !== viewports.xs && /*#__PURE__*/_jsx(Typography, {
118
137
  variant: {
119
- bold: true
138
+ size: 'micro'
120
139
  },
121
140
  tokens: {
122
- color: isPlaying ? titleColor : 'none'
141
+ color: subTitleColor
123
142
  },
124
- children: video.title
125
- })
126
- }), viewport !== viewports.xs && /*#__PURE__*/_jsx(Typography, {
127
- variant: {
128
- size: 'micro'
129
- },
130
- tokens: {
131
- color: subTitleColor
132
- },
133
- children: timestamp
134
- })]
135
- });
143
+ children: timestamp
144
+ })]
145
+ });
146
+ };
136
147
 
137
148
  const handleLayout = itemPositions !== undefined ? getItemPositionLayoutHandler(itemPositions.positions, index) : undefined;
138
149
 
@@ -155,14 +166,45 @@ const VideoPickerThumbnail = _ref6 => {
155
166
  accessibilityState: {
156
167
  checked: isPlaying
157
168
  },
158
- style: [rnStyles.container, layout === 'horizontal' && rnStyles.horizontal, isFramed && rnStyles.framed, isFramed && index > 0 && rnStyles.framedLine, {
159
- width
160
- }],
161
- children: /*#__PURE__*/_jsxs(StackView, {
162
- space: layout === 'vertical' ? 2 : 3,
163
- direction: layout === 'vertical' ? 'column' : 'row',
164
- children: [renderThumbnailImage(), renderThumbnailInfo()]
165
- })
169
+ style: _ref9 => {
170
+ let {
171
+ hovered: hover,
172
+ focused: focus,
173
+ pressed
174
+ } = _ref9;
175
+ const themeTokens = getTokens({
176
+ hover,
177
+ focus,
178
+ pressed,
179
+ selected: isPlaying
180
+ });
181
+ const rnStyles = createReactNativeStyles(themeTokens);
182
+ return [rnStyles.container, layout === 'horizontal' && rnStyles.horizontal, isFramed && rnStyles.framed, isFramed && index > 0 && rnStyles.framedLine, {
183
+ width
184
+ }, {
185
+ outline: 'none'
186
+ }];
187
+ },
188
+ children: _ref10 => {
189
+ let {
190
+ hovered: hover,
191
+ focused: focus,
192
+ pressed
193
+ } = _ref10;
194
+ const themeTokens = getTokens({
195
+ hover,
196
+ focus,
197
+ pressed,
198
+ selected: isPlaying
199
+ });
200
+ return /*#__PURE__*/_jsxs(StackView, {
201
+ space: layout === 'vertical' ? 2 : 3,
202
+ direction: layout === 'vertical' ? 'column' : 'row',
203
+ children: [/*#__PURE__*/_jsx(ImageContainer, { ...themeTokens,
204
+ children: renderThumbnailImage(themeTokens)
205
+ }), renderThumbnailInfo(themeTokens)]
206
+ });
207
+ }
166
208
  }, video.videoId);
167
209
  };
168
210
 
@@ -30,7 +30,7 @@ const VideoSlider = _ref => {
30
30
  setContainerWidth(width);
31
31
  };
32
32
 
33
- const itemsGap = 24; // '4' on spacing scale
33
+ const itemsGap = 32; // '5' on spacing scale
34
34
 
35
35
  const itemsCount = viewport === 'lg' || viewport === 'xl' ? 4 : 3;
36
36
  const itemGapPortioned = (itemsCount - 1) * itemsGap / itemsCount;
@@ -40,7 +40,7 @@ const VideoSlider = _ref => {
40
40
  );
41
41
 
42
42
  const content = /*#__PURE__*/_jsx(StackView, {
43
- space: 4,
43
+ space: 5,
44
44
  direction: "row",
45
45
  accessibilityRole: "radiogroup",
46
46
  tokens: {
package/package.json CHANGED
@@ -5,14 +5,14 @@
5
5
  ],
6
6
  "dependencies": {
7
7
  "@gorhom/portal": "^1.0.14",
8
- "@telus-uds/components-base": "1.53.0",
9
- "@telus-uds/system-constants": "^1.2.1",
8
+ "@telus-uds/components-base": "1.55.0",
9
+ "@telus-uds/system-constants": "^1.3.0",
10
10
  "fscreen": "^1.2.0",
11
11
  "lodash.omit": "^4.5.0",
12
12
  "react-dates": "^21.8.0",
13
13
  "react-helmet-async": "^1.3.0",
14
14
  "react-moment-proptypes": "^1.8.1",
15
- "@telus-uds/system-theme-tokens": "^2.36.0",
15
+ "@telus-uds/system-theme-tokens": "^2.38.0",
16
16
  "prop-types": "^15.7.2",
17
17
  "lodash.throttle": "^4.1.1",
18
18
  "react-youtube": "^10.1.0",
@@ -63,5 +63,5 @@
63
63
  "skip": true
64
64
  },
65
65
  "types": "types/index.d.ts",
66
- "version": "2.12.0"
66
+ "version": "2.14.0"
67
67
  }
@@ -1,15 +1,12 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { Box, StackView, Typography } from '@telus-uds/components-base'
3
+ import { Box, StackView } from '@telus-uds/components-base'
4
4
  import Spinner from '../Spinner'
5
5
 
6
6
  const Loading = ({ label }) => (
7
7
  <Box space={3}>
8
8
  <StackView direction="row" space={2} tokens={{ alignItems: 'center' }}>
9
- <Spinner inline={true} show={true} size="small" />
10
- <Typography variant={{ size: 'small' }} accessibilityLiveRegion="polite">
11
- {label}
12
- </Typography>
9
+ <Spinner inline={true} show={true} label={label} labelPosition="right" />
13
10
  </StackView>
14
11
  </Box>
15
12
  )
@@ -1,12 +1,12 @@
1
1
  export default {
2
2
  en: {
3
3
  hasResults: 'Some results are available',
4
- loading: 'Loading suggestions...',
4
+ loading: 'Searching...',
5
5
  noResults: 'No results found'
6
6
  },
7
7
  fr: {
8
8
  hasResults: 'Quelques suggestions sont disponible',
9
- loading: 'Chargement en cours...',
9
+ loading: 'Recherche...',
10
10
  noResults: 'Aucun résultat trouvé'
11
11
  }
12
12
  }
@@ -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
  }
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useState } from 'react'
1
+ import React, { forwardRef, useEffect, useState } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import styled from 'styled-components'
4
4
  import momentPropTypes from 'react-moment-proptypes'
@@ -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'
@@ -43,6 +44,7 @@ const getResponsiveCircleSize = (inline = false, viewport = 'md') => {
43
44
 
44
45
  return responsiveCircleSize
45
46
  }
47
+
46
48
  const MonthCenterContainer = styled.div({
47
49
  display: 'flex',
48
50
  justifyContent: 'center'
@@ -104,6 +106,16 @@ const DatePicker = forwardRef(
104
106
  const [isFocused, setIsFocused] = useState(false)
105
107
  const [isClickedInside, setIsClickedInside] = useState(false)
106
108
  const getCopy = useCopy({ dictionary, copy })
109
+ useEffect(() => {
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)) {
115
+ setInputDate(date)
116
+ setInputText(date instanceof moment ? date.format(dateFormat) : '')
117
+ }
118
+ }, [date, inputDate])
107
119
  const onFocusChange = ({ focused }) => {
108
120
  if (!isClickedInside) {
109
121
  setIsFocused(focused)
@@ -132,6 +144,7 @@ const DatePicker = forwardRef(
132
144
  const onChange = (value) => {
133
145
  setInputDate(value)
134
146
  setInputText(value.format(dateFormat))
147
+ setIsFocused(false)
135
148
  if (onDateChange) onDateChange(value)
136
149
  }
137
150
  const onChangeInput = (value) => {
@@ -1,6 +1,6 @@
1
1
  import React, { useRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { Icon, Spacer, useResponsiveProp, useThemeTokens } from '@telus-uds/components-base'
3
+ import { Icon, useResponsiveProp, useThemeTokens } from '@telus-uds/components-base'
4
4
  import NavigationItem from './NavigationItem'
5
5
  import Listbox from '../Listbox'
6
6
  import useOverlaidPosition from '../utils/useOverlaidPosition'
@@ -61,12 +61,11 @@ const NavigationSubMenu = ({
61
61
  >
62
62
  {({ textStyles }) => [
63
63
  children,
64
- <Spacer key={`${id}_spacer`} space={1} direction="row" />,
65
64
  <Icon
66
65
  key={`${id}_icon`}
67
66
  icon={icoMenu}
68
- variant={{ size: 'small' }}
69
- tokens={{ color: textStyles[0]?.color, size: textStyles[0]?.fontSize }}
67
+ variant={{ size: 'micro' }}
68
+ tokens={{ color: textStyles[0]?.color }}
70
69
  />
71
70
  ]}
72
71
  </NavigationItem>
@@ -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
  /**