@telus-uds/components-base 1.18.1 → 1.19.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 (36) hide show
  1. package/CHANGELOG.md +22 -2
  2. package/component-docs.json +111 -16
  3. package/jest.config-android.js +17 -0
  4. package/jest.config-ios.js +18 -0
  5. package/jest.config-web.js +31 -0
  6. package/lib/Button/ButtonBase.js +6 -2
  7. package/lib/Carousel/Carousel.js +32 -4
  8. package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +1 -10
  9. package/lib/Pagination/SideButton.js +6 -4
  10. package/lib/Responsive/Responsive.js +58 -0
  11. package/lib/Responsive/index.js +13 -0
  12. package/lib/Search/Search.js +29 -62
  13. package/lib/Tags/Tags.js +10 -4
  14. package/lib/TextInput/TextInputBase.js +53 -19
  15. package/lib/index.js +9 -0
  16. package/lib-module/Button/ButtonBase.js +6 -2
  17. package/lib-module/Carousel/Carousel.js +32 -4
  18. package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +1 -10
  19. package/lib-module/Pagination/SideButton.js +6 -4
  20. package/lib-module/Responsive/Responsive.js +45 -0
  21. package/lib-module/Responsive/index.js +2 -0
  22. package/lib-module/Search/Search.js +29 -60
  23. package/lib-module/Tags/Tags.js +10 -4
  24. package/lib-module/TextInput/TextInputBase.js +52 -19
  25. package/lib-module/index.js +1 -0
  26. package/package.json +6 -3
  27. package/src/Button/ButtonBase.jsx +4 -2
  28. package/src/Carousel/Carousel.jsx +42 -10
  29. package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +0 -10
  30. package/src/Pagination/SideButton.jsx +5 -5
  31. package/src/Responsive/Responsive.jsx +33 -0
  32. package/src/Responsive/index.js +3 -0
  33. package/src/Search/Search.jsx +17 -32
  34. package/src/Tags/Tags.jsx +46 -33
  35. package/src/TextInput/TextInputBase.jsx +46 -16
  36. package/src/index.js +1 -0
@@ -1,16 +1,13 @@
1
1
  import React, { forwardRef } from 'react';
2
2
  import View from "react-native-web/dist/exports/View";
3
- import StyleSheet from "react-native-web/dist/exports/StyleSheet";
4
3
  import PropTypes from 'prop-types';
5
4
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider';
6
5
  import { a11yProps, getTokensPropType, selectSystemProps, selectTokens, useInputValue, useSpacingScale, textInputHandlerProps, textInputProps, variantProp, viewProps } from '../utils';
7
6
  import TextInputBase from '../TextInput/TextInputBase';
8
7
  import ButtonBase from '../Button/ButtonBase';
9
- import StackView from '../StackView';
10
8
  import useCopy from '../utils/useCopy';
11
9
  import dictionary from './dictionary';
12
10
  import { jsx as _jsx } from "react/jsx-runtime";
13
- import { jsxs as _jsxs } from "react/jsx-runtime";
14
11
  const [selectContainerProps, selectedContainerPropTypes] = selectSystemProps([a11yProps, viewProps]);
15
12
  const [selectInputProps, selectedInputPropTypes] = selectSystemProps([textInputHandlerProps, textInputProps]);
16
13
 
@@ -39,20 +36,11 @@ const selectInputTokens = _ref => {
39
36
 
40
37
  const selectButtonTokens = tokens => selectTokens('Button', tokens);
41
38
 
42
- const selectIconsContainerStyle = _ref2 => {
43
- let {
44
- paddingRight
45
- } = _ref2;
46
- return {
47
- paddingRight
48
- };
49
- };
50
-
51
- const selectIconTokens = _ref3 => {
39
+ const selectIconTokens = _ref2 => {
52
40
  let {
53
41
  iconSize,
54
42
  iconColor
55
- } = _ref3;
43
+ } = _ref2;
56
44
  return {
57
45
  color: iconColor,
58
46
  size: iconSize
@@ -73,7 +61,7 @@ const selectIconTokens = _ref3 => {
73
61
  */
74
62
 
75
63
 
76
- const Search = /*#__PURE__*/forwardRef((_ref4, ref) => {
64
+ const Search = /*#__PURE__*/forwardRef((_ref3, ref) => {
77
65
  let {
78
66
  initialValue,
79
67
  value,
@@ -88,7 +76,7 @@ const Search = /*#__PURE__*/forwardRef((_ref4, ref) => {
88
76
  tokens,
89
77
  variant,
90
78
  ...rest
91
- } = _ref4;
79
+ } = _ref3;
92
80
  const {
93
81
  currentValue = '',
94
82
  setValue
@@ -140,10 +128,8 @@ const Search = /*#__PURE__*/forwardRef((_ref4, ref) => {
140
128
  testID,
141
129
  ...containerProps
142
130
  } = selectContainerProps(rest);
143
- return /*#__PURE__*/_jsxs(View, {
144
- style: staticStyles.container,
145
- ...containerProps,
146
- children: [/*#__PURE__*/_jsx(TextInputBase, {
131
+ return /*#__PURE__*/_jsx(View, { ...containerProps,
132
+ children: /*#__PURE__*/_jsx(TextInputBase, {
147
133
  nativeID: nativeID,
148
134
  testID: testID,
149
135
  ...selectInputProps(rest),
@@ -163,35 +149,29 @@ const Search = /*#__PURE__*/forwardRef((_ref4, ref) => {
163
149
  onChange: setValue,
164
150
  onSubmitEditing: handleSubmit,
165
151
  onFocus: handleFocus,
166
- accessibilityLabel: a11yLabelText
167
- }), /*#__PURE__*/_jsx(View, {
168
- style: [staticStyles.iconsContainer, selectIconsContainerStyle(themeTokens)],
169
- children: /*#__PURE__*/_jsxs(StackView, {
170
- direction: "row",
171
- space: buttonsGap,
172
- children: [ClearButtonIcon && !isEmpty && /*#__PURE__*/_jsx(ButtonBase, {
173
- onPress: handleClear,
174
- inactive: inactive,
175
- accessibilityRole: "button",
176
- accessibilityLabel: getCopy('clearButtonAccessibilityLabel'),
177
- tokens: appearances => selectButtonTokens(getButtonTokens(appearances)),
178
- children: buttonState => /*#__PURE__*/_jsx(ClearButtonIcon, { ...selectIconTokens(getButtonTokens(buttonState))
179
- })
180
- }), SubmitButtonIcon && /*#__PURE__*/_jsx(ButtonBase, {
181
- onPress: handleSubmit,
182
- inactive: inactive,
183
- accessibilityRole: "button",
184
- accessibilityLabel: getCopy('submitButtonAccessibilityLabel'),
185
- tokens: buttonState => selectButtonTokens(getButtonTokens({ ...buttonState,
152
+ accessibilityLabel: a11yLabelText,
153
+ buttons: [ClearButtonIcon && !isEmpty && /*#__PURE__*/_jsx(ButtonBase, {
154
+ accessibilityLabel: getCopy('clearButtonAccessibilityLabel'),
155
+ accessibilityRole: "button",
156
+ inactive: inactive,
157
+ onPress: handleClear,
158
+ tokens: appearances => selectButtonTokens(getButtonTokens(appearances)),
159
+ children: buttonState => /*#__PURE__*/_jsx(ClearButtonIcon, { ...selectIconTokens(getButtonTokens(buttonState))
160
+ })
161
+ }, "clear"), SubmitButtonIcon && /*#__PURE__*/_jsx(ButtonBase, {
162
+ accessibilityLabel: getCopy('submitButtonAccessibilityLabel'),
163
+ accessibilityRole: "button",
164
+ inactive: inactive,
165
+ onPress: handleSubmit,
166
+ tokens: buttonState => selectButtonTokens(getButtonTokens({ ...buttonState,
167
+ priority: 'high'
168
+ })),
169
+ children: buttonState => /*#__PURE__*/_jsx(SubmitButtonIcon, { ...selectIconTokens(getButtonTokens({ ...buttonState,
186
170
  priority: 'high'
187
- })),
188
- children: buttonState => /*#__PURE__*/_jsx(SubmitButtonIcon, { ...selectIconTokens(getButtonTokens({ ...buttonState,
189
- priority: 'high'
190
- }))
191
- })
192
- })]
193
- })
194
- })]
171
+ }))
172
+ })
173
+ }, "submit")]
174
+ })
195
175
  });
196
176
  });
197
177
  Search.displayName = 'Search';
@@ -255,15 +235,4 @@ Search.propTypes = { ...selectedContainerPropTypes,
255
235
  tokens: getTokensPropType('Search'),
256
236
  variant: variantProp.propType
257
237
  };
258
- export default Search;
259
- const staticStyles = StyleSheet.create({
260
- container: {// No styles needed here except the View defaults (position: relative etc)
261
- },
262
- iconsContainer: {
263
- position: 'absolute',
264
- right: 0,
265
- top: 0,
266
- bottom: 0,
267
- justifyContent: 'center'
268
- }
269
- });
238
+ export default Search;
@@ -15,7 +15,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
15
15
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
16
16
  const [selectItemProps, selectedItemPropTypes] = selectSystemProps([a11yProps, focusHandlerProps, pressProps, viewProps]);
17
17
 
18
- const selectIconTextTokens = _ref => {
18
+ const separateIconTextTokens = (_ref, returnRest) => {
19
19
  let {
20
20
  icon,
21
21
  iconPosition,
@@ -27,9 +27,10 @@ const selectIconTextTokens = _ref => {
27
27
  iconAlignSelf,
28
28
  iconPadding,
29
29
  iconTranslateX,
30
- iconTranslateY
30
+ iconTranslateY,
31
+ ...rest
31
32
  } = _ref;
32
- return {
33
+ return returnRest ? rest : {
33
34
  icon,
34
35
  iconPosition,
35
36
  iconSpace,
@@ -54,6 +55,10 @@ const selectIconTextTokens = _ref => {
54
55
  };
55
56
  };
56
57
 
58
+ const selectIconTextTokens = tokens => separateIconTextTokens(tokens, false);
59
+
60
+ const selectNonIconTextTokens = tokens => separateIconTextTokens(tokens, true);
61
+
57
62
  const Tags = /*#__PURE__*/forwardRef((_ref2, ref) => {
58
63
  let {
59
64
  variant,
@@ -82,7 +87,8 @@ const Tags = /*#__PURE__*/forwardRef((_ref2, ref) => {
82
87
  } = themeTokens;
83
88
  const getItemTokens = useThemeTokensCallback('TagsItem', tokens, variant);
84
89
 
85
- const getButtonTokens = buttonState => selectTokens('Button', getItemTokens(buttonState));
90
+ const getButtonTokens = buttonState => // Remove icon-text-related tokens, since we want to handle them ourselves, not use ButtonBase's handling
91
+ selectTokens('Button', selectNonIconTextTokens(getItemTokens(buttonState)));
86
92
 
87
93
  const {
88
94
  currentValues,
@@ -1,11 +1,12 @@
1
1
  import React, { forwardRef, useEffect, useState } from 'react';
2
+ import PropTypes from 'prop-types';
2
3
  import Platform from "react-native-web/dist/exports/Platform";
3
4
  import StyleSheet from "react-native-web/dist/exports/StyleSheet";
4
5
  import NativeTextInput from "react-native-web/dist/exports/TextInput";
5
6
  import View from "react-native-web/dist/exports/View";
6
- import PropTypes from 'prop-types';
7
7
  import { applyTextStyles, useTheme, useThemeTokens, applyOuterBorder } from '../ThemeProvider';
8
- import { a11yProps, getTokensPropType, selectSystemProps, textInputHandlerProps, textInputProps, useInputValue, variantProp, viewProps } from '../utils';
8
+ import StackView from '../StackView';
9
+ import { a11yProps, getTokensPropType, selectSystemProps, textInputHandlerProps, textInputProps, useInputValue, useSpacingScale, variantProp, viewProps } from '../utils';
9
10
  import { jsx as _jsx } from "react/jsx-runtime";
10
11
  import { jsxs as _jsxs } from "react/jsx-runtime";
11
12
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, textInputHandlerProps, textInputProps, viewProps]);
@@ -110,35 +111,47 @@ const selectIconTokens = _ref3 => {
110
111
  };
111
112
  };
112
113
 
113
- const selectIconContainerStyles = _ref4 => {
114
+ const selectIconContainerStyles = (_ref4, buttonCount) => {
114
115
  let {
116
+ buttonSize,
117
+ buttonsGapSize,
115
118
  paddingRight,
116
119
  paddingBottom
117
120
  } = _ref4;
118
121
  return {
119
- paddingRight,
122
+ paddingRight: paddingRight + buttonCount * (buttonSize + buttonsGapSize),
120
123
  paddingBottom
121
124
  };
122
125
  };
123
126
 
124
- const TextInputBase = /*#__PURE__*/forwardRef((_ref5, ref) => {
127
+ const selectButtonsContainerStyle = _ref5 => {
125
128
  let {
126
- value,
129
+ buttonsPaddingRight
130
+ } = _ref5;
131
+ return {
132
+ paddingRight: buttonsPaddingRight
133
+ };
134
+ };
135
+
136
+ const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
137
+ let {
138
+ buttons = [],
127
139
  height,
128
- initialValue,
129
140
  inactive,
130
- readOnly,
141
+ initialValue,
142
+ onBlur,
131
143
  onChange,
132
144
  onChangeText,
133
145
  onFocus,
134
- onBlur,
135
- onMouseOver,
136
146
  onMouseOut,
147
+ onMouseOver,
137
148
  pattern,
149
+ readOnly,
138
150
  tokens,
151
+ value,
139
152
  variant = {},
140
153
  ...rest
141
- } = _ref5;
154
+ } = _ref6;
142
155
  const [isFocused, setIsFocused] = useState(false);
143
156
 
144
157
  const handleFocus = event => {
@@ -197,7 +210,8 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref5, ref) => {
197
210
  };
198
211
  const themeTokens = useThemeTokens('TextInput', tokens, variant, states);
199
212
  const {
200
- icon: IconComponent
213
+ icon: IconComponent,
214
+ buttonsGap
201
215
  } = themeTokens;
202
216
  const inputProps = { ...selectProps(rest),
203
217
  editable: !inactive,
@@ -210,7 +224,9 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref5, ref) => {
210
224
  // currentValue is being updated even if the input is not controlled, passing it down to the
211
225
  // Input could lead to changing its state from uncontrolled to controlled
212
226
  value: isControlled ? currentValue : undefined
213
- };
227
+ }; // Get the actual gap value for the current viewport
228
+
229
+ const buttonsGapSize = useSpacingScale(buttonsGap);
214
230
  const {
215
231
  themeOptions
216
232
  } = useTheme();
@@ -226,30 +242,47 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref5, ref) => {
226
242
  }), IconComponent && /*#__PURE__*/_jsx(View, {
227
243
  pointerEvents: "none" // avoid hijacking input press events
228
244
  ,
229
- style: [staticStyles.iconContainer, selectIconContainerStyles(themeTokens)],
245
+ style: [staticStyles.iconContainer, selectIconContainerStyles({ ...themeTokens,
246
+ buttonsGapSize
247
+ }, buttons === null || buttons === void 0 ? void 0 : buttons.length)],
230
248
  children: /*#__PURE__*/_jsx(IconComponent, { ...selectIconTokens(themeTokens)
231
249
  })
250
+ }), (buttons === null || buttons === void 0 ? void 0 : buttons.length) > 0 && /*#__PURE__*/_jsx(View, {
251
+ style: [staticStyles.buttonsContainer, selectButtonsContainerStyle(themeTokens)],
252
+ children: /*#__PURE__*/_jsx(StackView, {
253
+ direction: "row",
254
+ space: buttonsGap,
255
+ children: buttons
256
+ })
232
257
  })]
233
258
  });
234
259
  });
235
260
  TextInputBase.displayName = 'TextInputBase';
236
261
  TextInputBase.propTypes = { ...selectedSystemPropTypes,
237
- value: PropTypes.string,
262
+ buttons: PropTypes.arrayOf(PropTypes.node),
238
263
  height: PropTypes.number,
239
- initialValue: PropTypes.string,
240
264
  inactive: PropTypes.bool,
241
- readOnly: PropTypes.bool,
265
+ initialValue: PropTypes.string,
266
+ onBlur: PropTypes.func,
242
267
  onChange: PropTypes.func,
243
268
  onChangeText: PropTypes.func,
244
269
  onFocus: PropTypes.func,
245
- onBlur: PropTypes.func,
246
- onMouseOver: PropTypes.func,
247
270
  onMouseOut: PropTypes.func,
271
+ onMouseOver: PropTypes.func,
272
+ readOnly: PropTypes.bool,
248
273
  tokens: getTokensPropType('TextInput', 'TextArea'),
274
+ value: PropTypes.string,
249
275
  variant: variantProp.propType
250
276
  };
251
277
  export default TextInputBase;
252
278
  const staticStyles = StyleSheet.create({
279
+ buttonsContainer: {
280
+ position: 'absolute',
281
+ right: 0,
282
+ top: 0,
283
+ bottom: 0,
284
+ justifyContent: 'center'
285
+ },
253
286
  iconContainer: {
254
287
  position: 'absolute',
255
288
  right: 0,
@@ -29,6 +29,7 @@ export { default as Radio } from './Radio';
29
29
  export * from './Radio';
30
30
  export { default as RadioCard } from './RadioCard';
31
31
  export * from './RadioCard';
32
+ export { default as Responsive } from './Responsive';
32
33
  export { default as Search } from './Search';
33
34
  export { default as Select } from './Select';
34
35
  export { default as SideNav } from './SideNav';
package/package.json CHANGED
@@ -8,8 +8,8 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@gorhom/portal": "^1.0.14",
11
- "@telus-uds/system-constants": "^1.0.4",
12
- "@telus-uds/system-theme-tokens": "^2.6.0",
11
+ "@telus-uds/system-constants": "^1.1.0",
12
+ "@telus-uds/system-theme-tokens": "^2.7.0",
13
13
  "airbnb-prop-types": "^2.16.0",
14
14
  "lodash.debounce": "^4.0.8",
15
15
  "lodash.merge": "^4.6.2",
@@ -54,6 +54,9 @@
54
54
  },
55
55
  "scripts": {
56
56
  "test": "jest",
57
+ "test:web": "jest --config jest.config-web.js",
58
+ "test:ios": "jest --config jest.config-ios.js",
59
+ "test:android": "jest --config jest.config-android.js",
57
60
  "lint": "npm run --prefix ../.. lint:path -- --color packages/components-base",
58
61
  "lint:fix": "npm run --prefix ../.. lint:path -- --fix packages/components-base",
59
62
  "format": "prettier --write .",
@@ -67,5 +70,5 @@
67
70
  "standard-engine": {
68
71
  "skip": true
69
72
  },
70
- "version": "1.18.1"
73
+ "version": "1.19.0"
71
74
  }
@@ -123,7 +123,7 @@ const selectBorderStyles = ({ borderColor, borderWidth, borderRadius }) => ({
123
123
  })
124
124
 
125
125
  const selectTextStyles = (
126
- { fontSize, color, lineHeight, fontName, fontWeight, textAlign },
126
+ { fontSize, color, lineHeight, fontName, fontWeight, textAlign, textLine, textLineStyle },
127
127
  themeOptions
128
128
  ) =>
129
129
  applyTextStyles({
@@ -133,7 +133,9 @@ const selectTextStyles = (
133
133
  fontName,
134
134
  fontWeight,
135
135
  themeOptions,
136
- textAlign
136
+ textAlign,
137
+ textDecorationLine: textLine,
138
+ textDecorationStyle: textLineStyle
137
139
  })
138
140
 
139
141
  const selectWebOnlyStyles = (inactive, themeTokens, { accessibilityRole }) => {
@@ -164,7 +164,7 @@ const Carousel = React.forwardRef(
164
164
  ),
165
165
  tag = 'ul',
166
166
  accessibilityRole,
167
- accessibilityLabel = title,
167
+ accessibilityLabel,
168
168
  accessibilityLiveRegion = 'polite',
169
169
  copy,
170
170
  ...rest
@@ -422,6 +422,32 @@ const Carousel = React.forwardRef(
422
422
  const activePanelNavigation =
423
423
  tabs && showPanelTabs ? <CarouselTabsPanel items={tabs} /> : panelNavigation
424
424
 
425
+ const isFirstFocusContainer = Boolean(refocus && !skipLinkHref)
426
+ const containerRef = (element) => {
427
+ // Apply both firstFocusRef to the container
428
+ firstFocusRef.current = element
429
+ // Also apply forwarded ref if there is one (which could be a function ref)
430
+ if (ref) {
431
+ if (typeof ref === 'object') {
432
+ // eslint-disable-next-line no-param-reassign
433
+ ref.current = element
434
+ } else if (typeof ref === 'function') {
435
+ ref(element)
436
+ }
437
+ }
438
+ }
439
+ // If container isn't used for focus, give it a label of title if none is passed in,
440
+ // otherwise read the current position on focus
441
+ const containerAccessibilityLabel =
442
+ systemProps.accessibilityLabel ?? isFirstFocusContainer
443
+ ? `${title ? `${title} ` : ''}${getCopyWithPlaceholders('stepTrackerLabel')}`
444
+ : title
445
+ const containerProps = {
446
+ accessibilityLabel: containerAccessibilityLabel,
447
+ // If used for focus, attach the ref and draw a focus box around the whole carousel
448
+ ...(isFirstFocusContainer && { ref: containerRef, focusable: true })
449
+ }
450
+
425
451
  return (
426
452
  <CarouselProvider
427
453
  activeIndex={activeIndex}
@@ -434,7 +460,13 @@ const Carousel = React.forwardRef(
434
460
  refocus={refocus}
435
461
  width={containerLayout.width}
436
462
  >
437
- <View style={staticStyles.root} onLayout={onContainerLayout} ref={ref} {...systemProps}>
463
+ <View
464
+ style={staticStyles.root}
465
+ onLayout={onContainerLayout}
466
+ ref={ref}
467
+ {...systemProps}
468
+ {...containerProps}
469
+ >
438
470
  {showPreviousNextNavigation && (
439
471
  <View
440
472
  style={selectPreviousNextNavigationButtonStyles(
@@ -464,14 +496,14 @@ const Carousel = React.forwardRef(
464
496
  {getCopyWithPlaceholders('skipLink')}
465
497
  </SkipLink>
466
498
  )}
467
- <A11yText
468
- // Read the current slide position to screen readers on slide.
469
- // If it's set to refocus and doesn't have a SkipLink to focus to, focus this.
470
- ref={!skipLinkHref && refocus ? firstFocusRef : null}
471
- accessibilityLiveRegion={!skipLinkHref && refocus ? undefined : 'polite'}
472
- focusable={!skipLinkHref && refocus}
473
- text={getCopyWithPlaceholders('stepTrackerLabel')}
474
- />
499
+ {!isFirstFocusContainer && (
500
+ <A11yText
501
+ // Read the current slide position to screen readers on slide.
502
+ // If it's set to refocus and doesn't have a SkipLink to focus to, focus this.
503
+ accessibilityLiveRegion={!skipLinkHref && refocus ? undefined : 'polite'}
504
+ text={getCopyWithPlaceholders('stepTrackerLabel')}
505
+ />
506
+ )}
475
507
  <View style={selectContainerStyles(containerLayout.width)}>
476
508
  <Animated.View
477
509
  style={StyleSheet.flatten([
@@ -21,16 +21,6 @@ const CarouselTabsPanel = forwardRef(({ items }, ref) => {
21
21
 
22
22
  return (
23
23
  <>
24
- <View
25
- focusable
26
- accessible
27
- onFocus={(event) => {
28
- // When user forward-tabs into this section, focus the next tab; if they backwards-tab
29
- // (shift-tab) back into the carousel content, don't interfere.
30
- const previousWebFocus = event.relatedTarget
31
- if (previousWebFocus !== firstTabRef.current) nextFocusRef.current.focus()
32
- }}
33
- />
34
24
  <StackView direction="row" space={3} divider={{ variant: dividerVariant }} ref={ref}>
35
25
  {items.map(({ title, onPress, ...panelItemProps }, index) => {
36
26
  const selected = index === activeIndex
@@ -13,8 +13,9 @@ import dictionary from './dictionary'
13
13
  import useCopy from '../utils/useCopy'
14
14
 
15
15
  // We need to drop the icon here since it gets rendered via children and not
16
- // `ButtonBase` in order to tap into the state of the button
17
- const selectButtonTokens = ({ icon: _, ...rest }) => selectTokens('Button', rest)
16
+ // `ButtonBase` in order to tap into the state of the button; `displayLabel` flag
17
+ // is also not needed
18
+ const selectButtonTokens = ({ icon: _, displayLabel: __, ...rest }) => selectTokens('Button', rest)
18
19
  const selectIconTokens = ({ color, iconSize, iconDisplace }, direction) => {
19
20
  return {
20
21
  color,
@@ -36,13 +37,12 @@ const SideButton = forwardRef(
36
37
 
37
38
  const getCopy = useCopy({ dictionary, copy })
38
39
 
39
- const { icon } = getTokens(tokens, buttonVariant)
40
+ const { icon, displayLabel } = getTokens(tokens, buttonVariant)
40
41
 
41
42
  const getButtonTokens = (buttonState) => selectButtonTokens(getTokens(buttonState))
42
43
  const getIconTokens = (buttonState) => selectIconTokens(getTokens(buttonState), direction)
43
44
 
44
45
  const label = direction === 'previous' ? getCopy('previousText') : getCopy('nextText')
45
- const showLabel = viewport !== 'sm' && viewport !== 'xs'
46
46
 
47
47
  const accessibilityLabel =
48
48
  direction === 'previous' ? getCopy('previousLabel') : getCopy('nextLabel')
@@ -69,7 +69,7 @@ const SideButton = forwardRef(
69
69
  iconPosition={directionToSide[direction]}
70
70
  iconProps={iconProps}
71
71
  >
72
- {showLabel && <Text style={textStyles}>{label}</Text>}
72
+ {displayLabel && <Text style={textStyles}>{label}</Text>}
73
73
  </IconText>
74
74
  )
75
75
  }}
@@ -0,0 +1,33 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { viewports } from '@telus-uds/system-constants'
4
+ import { useResponsiveProp } from '../utils'
5
+
6
+ /**
7
+ * Responsive conditionally renders children based on whether the viewport matches the provided
8
+ * min and max viewports.
9
+ *
10
+ * In SSR, like other viewport utilities, it treats the viewport as `xs` both in SSR itself and
11
+ * during first hydration on the client side; then if the viewport is not `xs`, it re-renders
12
+ * after hydration. This may cause a layout shift on devices other than the narrowest.
13
+ */
14
+
15
+ const Responsive = ({ min = 'xs', max, children }) => {
16
+ // Start returning children at the 'min' viewport or greater
17
+ const byViewports = { [min]: children }
18
+ if (max && max !== 'xl') {
19
+ // Stop returning children at the viewport one above 'max' or greater
20
+ const maxIndex = viewports.keys.indexOf(max)
21
+ const maxPlusOne = maxIndex >= 0 ? viewports.keys[maxIndex + 1] : null
22
+ if (maxPlusOne) byViewports[maxPlusOne] = null
23
+ }
24
+ return <>{useResponsiveProp(byViewports, null)}</>
25
+ }
26
+
27
+ Responsive.propTypes = {
28
+ min: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
29
+ max: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
30
+ children: PropTypes.node.isRequired
31
+ }
32
+
33
+ export default Responsive
@@ -0,0 +1,3 @@
1
+ import Responsive from './Responsive'
2
+
3
+ export default Responsive
@@ -1,5 +1,5 @@
1
1
  import React, { forwardRef } from 'react'
2
- import { View, StyleSheet } from 'react-native'
2
+ import { View } from 'react-native'
3
3
 
4
4
  import PropTypes from 'prop-types'
5
5
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider'
@@ -17,7 +17,6 @@ import {
17
17
  } from '../utils'
18
18
  import TextInputBase from '../TextInput/TextInputBase'
19
19
  import ButtonBase from '../Button/ButtonBase'
20
- import StackView from '../StackView'
21
20
  import useCopy from '../utils/useCopy'
22
21
  import dictionary from './dictionary'
23
22
 
@@ -42,7 +41,6 @@ const selectInputTokens = ({ searchTokens, buttonTokens, buttonsGapSize }) => {
42
41
  }
43
42
  const selectButtonTokens = (tokens) => selectTokens('Button', tokens)
44
43
 
45
- const selectIconsContainerStyle = ({ paddingRight }) => ({ paddingRight })
46
44
  const selectIconTokens = ({ iconSize, iconColor }) => ({ color: iconColor, size: iconSize })
47
45
 
48
46
  /**
@@ -126,7 +124,7 @@ const Search = forwardRef(
126
124
  const { nativeID, testID, ...containerProps } = selectContainerProps(rest)
127
125
 
128
126
  return (
129
- <View style={staticStyles.container} {...containerProps}>
127
+ <View {...containerProps}>
130
128
  <TextInputBase
131
129
  nativeID={nativeID}
132
130
  testID={testID}
@@ -150,28 +148,28 @@ const Search = forwardRef(
150
148
  onSubmitEditing={handleSubmit}
151
149
  onFocus={handleFocus}
152
150
  accessibilityLabel={a11yLabelText}
153
- />
154
- <View style={[staticStyles.iconsContainer, selectIconsContainerStyle(themeTokens)]}>
155
- <StackView direction="row" space={buttonsGap}>
156
- {ClearButtonIcon && !isEmpty && (
151
+ buttons={[
152
+ ClearButtonIcon && !isEmpty && (
157
153
  <ButtonBase
158
- onPress={handleClear}
159
- inactive={inactive}
160
- accessibilityRole="button"
161
154
  accessibilityLabel={getCopy('clearButtonAccessibilityLabel')}
155
+ accessibilityRole="button"
156
+ inactive={inactive}
157
+ key="clear"
158
+ onPress={handleClear}
162
159
  tokens={(appearances) => selectButtonTokens(getButtonTokens(appearances))}
163
160
  >
164
161
  {(buttonState) => (
165
162
  <ClearButtonIcon {...selectIconTokens(getButtonTokens(buttonState))} />
166
163
  )}
167
164
  </ButtonBase>
168
- )}
169
- {SubmitButtonIcon && (
165
+ ),
166
+ SubmitButtonIcon && (
170
167
  <ButtonBase
171
- onPress={handleSubmit}
172
- inactive={inactive}
173
- accessibilityRole="button"
174
168
  accessibilityLabel={getCopy('submitButtonAccessibilityLabel')}
169
+ accessibilityRole="button"
170
+ inactive={inactive}
171
+ key="submit"
172
+ onPress={handleSubmit}
175
173
  tokens={(buttonState) =>
176
174
  selectButtonTokens(getButtonTokens({ ...buttonState, priority: 'high' }))
177
175
  }
@@ -182,9 +180,9 @@ const Search = forwardRef(
182
180
  />
183
181
  )}
184
182
  </ButtonBase>
185
- )}
186
- </StackView>
187
- </View>
183
+ )
184
+ ]}
185
+ />
188
186
  </View>
189
187
  )
190
188
  }
@@ -248,16 +246,3 @@ Search.propTypes = {
248
246
  }
249
247
 
250
248
  export default Search
251
-
252
- const staticStyles = StyleSheet.create({
253
- container: {
254
- // No styles needed here except the View defaults (position: relative etc)
255
- },
256
- iconsContainer: {
257
- position: 'absolute',
258
- right: 0,
259
- top: 0,
260
- bottom: 0,
261
- justifyContent: 'center'
262
- }
263
- })