@telus-uds/components-web 4.0.0 → 4.1.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.
package/CHANGELOG.md CHANGED
@@ -1,12 +1,28 @@
1
1
  # Change Log - @telus-uds/components-web
2
2
 
3
- This log was last generated on Fri, 31 Jan 2025 20:49:19 GMT and should not be manually modified.
3
+ This log was last generated on Mon, 10 Feb 2025 17:30:58 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 4.1.0
8
+
9
+ Mon, 10 Feb 2025 17:30:58 GMT
10
+
11
+ ### Minor changes
12
+
13
+ - `PriceLockup`: aria-label added for all the group of elements (35577399+JoshHC@users.noreply.github.com)
14
+ - Bump @telus-uds/components-base to v3.1.0
15
+ - Bump @telus-uds/system-theme-tokens to v4.1.0
16
+
17
+ ### Patches
18
+
19
+ - QuantitySelector: update snapshot tests (guillermo.peitzner@telus.com)
20
+ - `Table`: new tokens added at row and cell level to achieve new designs (35577399+JoshHC@users.noreply.github.com)
21
+ - `Listbox`: Include export type members for components-web (jaime.tuyuc@telus.com)
22
+
7
23
  ## 4.0.0
8
24
 
9
- Fri, 31 Jan 2025 20:49:19 GMT
25
+ Fri, 31 Jan 2025 20:53:28 GMT
10
26
 
11
27
  ### Major changes
12
28
 
@@ -11,9 +11,10 @@ var _styledComponents = _interopRequireDefault(require("styled-components"));
11
11
  var _FootnoteLink = _interopRequireDefault(require("../Footnote/FootnoteLink"));
12
12
  var _tokens = _interopRequireDefault(require("./tokens"));
13
13
  var _utils = require("../utils");
14
+ var _dictionary = _interopRequireDefault(require("./dictionary"));
14
15
  var _jsxRuntime = require("react/jsx-runtime");
15
16
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
- const [selectProps, selectedSystemPropTypes] = (0, _componentsBase.selectSystemProps)([_utils.htmlAttrs]);
17
+ const [selectProps, selectedSystemPropTypes] = (0, _componentsBase.selectSystemProps)([_utils.htmlAttrs, _componentsBase.a11yProps]);
17
18
  const PriceLockupContainer = /*#__PURE__*/_styledComponents.default.div.withConfig({
18
19
  displayName: "PriceLockup__PriceLockupContainer",
19
20
  componentId: "components-web__sc-1x6duay-0"
@@ -23,7 +24,9 @@ const PriceLockupContainer = /*#__PURE__*/_styledComponents.default.div.withConf
23
24
  } = _ref;
24
25
  return alignItemsText;
25
26
  });
26
- const PriceContainer = /*#__PURE__*/_styledComponents.default.div.withConfig({
27
+ const PriceContainer = /*#__PURE__*/_styledComponents.default.div.attrs({
28
+ 'aria-hidden': 'true'
29
+ }).withConfig({
27
30
  displayName: "PriceLockup__PriceContainer",
28
31
  componentId: "components-web__sc-1x6duay-1"
29
32
  })(["display:flex;margin-bottom:", ";"], _ref2 => {
@@ -64,7 +67,9 @@ const BottomLinksContainer = /*#__PURE__*/_styledComponents.default.div.withConf
64
67
  } = _ref6;
65
68
  return bottomLinksMarginLeft;
66
69
  });
67
- const TopTextContainer = /*#__PURE__*/_styledComponents.default.div.withConfig({
70
+ const TopTextContainer = /*#__PURE__*/_styledComponents.default.div.attrs({
71
+ 'aria-hidden': 'true'
72
+ }).withConfig({
68
73
  displayName: "PriceLockup__TopTextContainer",
69
74
  componentId: "components-web__sc-1x6duay-5"
70
75
  })(["margin-bottom:", ";"], _ref7 => {
@@ -165,6 +170,8 @@ const PriceLockup = /*#__PURE__*/_react.default.forwardRef((_ref16, ref) => {
165
170
  a11yText,
166
171
  tokens: priceLockupTokens,
167
172
  variant = {},
173
+ copy = 'en',
174
+ dictionary = _dictionary.default,
168
175
  ...rest
169
176
  } = _ref16;
170
177
  const viewport = (0, _componentsBase.useViewport)();
@@ -277,10 +284,28 @@ const PriceLockup = /*#__PURE__*/_react.default.forwardRef((_ref16, ref) => {
277
284
  if (strikeThrough && !a11yText) {
278
285
  (0, _utils.warn)('PriceLockup', 'a11yText must be provided with strikethrough pricing');
279
286
  }
287
+ const getAriaContent = () => {
288
+ const {
289
+ dictionaryPrice = price,
290
+ priceWithCents,
291
+ rate
292
+ } = dictionary[copy];
293
+ let ariaLabel = hasCents ? priceWithCents.replace('%{amount}', amount).replace('%{cents}', cents).replace('%{currency}', currencySymbol) : dictionaryPrice.replace('%{amount}', amount).replace('%{currency}', currencySymbol);
294
+ if (!ariaLabel.includes(currencySymbol)) {
295
+ ariaLabel = `${ariaLabel} ${currencySymbol}`;
296
+ }
297
+ if (rateText) {
298
+ ariaLabel += ` ${rate.replace('%{rateText}', rateText.replace('/', ''))}`;
299
+ }
300
+ return ariaLabel;
301
+ };
302
+ const ariaLabel = selectProps(rest)['aria-label'] ?? getAriaContent();
280
303
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(PriceLockupContainer, {
281
304
  ...selectProps(rest),
282
305
  alignItemsText: alignItemsText,
283
306
  ref: ref,
307
+ role: "group",
308
+ "aria-label": ariaLabel,
284
309
  children: [topText && /*#__PURE__*/(0, _jsxRuntime.jsx)(TopTextContainer, {
285
310
  topTextMarginBottom: `${topTextMarginBottom}px`,
286
311
  children: renderTypography(topText, typographyTokens.topText)
@@ -294,6 +319,13 @@ const PriceLockup = /*#__PURE__*/_react.default.forwardRef((_ref16, ref) => {
294
319
  });
295
320
  });
296
321
  PriceLockup.displayName = 'PriceLockup';
322
+
323
+ // If a language dictionary entry is provided, it must contain every key
324
+ const dictionaryContentShape = _propTypes.default.shape({
325
+ price: _propTypes.default.string.isRequired,
326
+ priceWithCents: _propTypes.default.string.isRequired,
327
+ rate: _propTypes.default.string.isRequired
328
+ });
297
329
  PriceLockup.propTypes = {
298
330
  ...selectedSystemPropTypes,
299
331
  /**
@@ -348,6 +380,16 @@ PriceLockup.propTypes = {
348
380
  * **Note:** a11yText will override strikethrough price, so it must include price (ie. "was 50 dollars per month")
349
381
  */
350
382
  a11yText: _propTypes.default.string,
383
+ /**
384
+ * Select English or French copy for the accessible label.
385
+ */
386
+ copy: _propTypes.default.oneOf(['en', 'fr']),
387
+ /* Custom dictionary containing the labels
388
+ */
389
+ dictionary: _propTypes.default.shape({
390
+ en: dictionaryContentShape,
391
+ fr: dictionaryContentShape
392
+ }),
351
393
  /**
352
394
  * `PriceLockup` tokens
353
395
  */
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _default = exports.default = {
8
+ en: {
9
+ price: '%{amount} %{currency}',
10
+ priceWithCents: '%{amount} %{currency} and %{cents} cents',
11
+ rate: 'per %{rateText}'
12
+ },
13
+ fr: {
14
+ price: '%{amount} %{currency}',
15
+ priceWithCents: '%{amount} %{currency} et %{cents} centimes',
16
+ rate: 'par %{rateText}'
17
+ }
18
+ };
@@ -34,17 +34,20 @@ const sharedStyles = /*#__PURE__*/(0, _styledComponents.css)(["", ""], _ref2 =>
34
34
  cellPaddingRight,
35
35
  cellPaddingBottom,
36
36
  cellPaddingLeft,
37
+ cellBorderWidth,
38
+ cellBorderColor,
39
+ cellBorderTopWidth,
37
40
  cellMinWidth,
38
41
  cellBackground,
39
42
  cellStickyShadow,
40
43
  stickyBackgroundColor,
41
44
  type
42
45
  } = _ref2;
43
- return (0, _styledComponents.css)(["text-align:", ";min-width:", "px;padding:", "px ", "px ", "px ", "px;background-color:", ";", ";"], align, cellMinWidth, cellPaddingTop, cellPaddingRight, cellPaddingBottom, cellPaddingLeft, cellBackground, isSticky && stickyStyles({
46
+ return (0, _styledComponents.css)(["text-align:", ";min-width:", "px;padding:", "px ", "px ", "px ", "px;background-color:", ";", ";border-style:", ";border-color:", ";border-width:", ";border-top-width:", ";"], align, cellMinWidth, cellPaddingTop, cellPaddingRight, cellPaddingBottom, cellPaddingLeft, cellBackground, isSticky && stickyStyles({
44
47
  type,
45
48
  cellStickyShadow,
46
49
  stickyBackgroundColor
47
- }));
50
+ }), cellBorderWidth || cellBorderTopWidth ? 'solid' : 'none', cellBorderColor, cellBorderWidth, cellBorderTopWidth || cellBorderWidth);
48
51
  });
49
52
  const createStyledCell = htmlElement => _styledComponents.default[htmlElement].withConfig({
50
53
  displayName: "Cell__createStyledCell",
@@ -93,6 +96,9 @@ const Cell = /*#__PURE__*/_react.default.forwardRef((_ref5, ref) => {
93
96
  cellPaddingRight,
94
97
  cellPaddingLeft,
95
98
  cellPaddingBottom,
99
+ cellBorderWidth,
100
+ cellBorderColor,
101
+ cellBorderTopWidth,
96
102
  fontName,
97
103
  fontWeight,
98
104
  fontSize,
@@ -116,6 +122,9 @@ const Cell = /*#__PURE__*/_react.default.forwardRef((_ref5, ref) => {
116
122
  cellPaddingRight,
117
123
  cellPaddingLeft,
118
124
  cellPaddingBottom,
125
+ cellBorderWidth,
126
+ cellBorderColor,
127
+ cellBorderTopWidth,
119
128
  stickyBackgroundColor,
120
129
  cellBoxShadowColor,
121
130
  display,
@@ -13,22 +13,47 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
13
13
  const StyledTR = /*#__PURE__*/_styledComponents.default.tr.withConfig({
14
14
  displayName: "Row__StyledTR",
15
15
  componentId: "components-web__sc-6pbb9a-0"
16
- })(["&:hover{background-color:", " !important;}"], _ref => {
16
+ })(["&:hover{background-color:", " !important;}border-style:", ";border-color:", ";border-width:", ";border-top-width:", ";"], _ref => {
17
17
  let {
18
- rowHoverBackgroundColor
18
+ tokens
19
19
  } = _ref;
20
- return rowHoverBackgroundColor;
21
- });
22
- const Row = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
20
+ return tokens?.rowHoverBackgroundColor;
21
+ }, _ref2 => {
23
22
  let {
24
- children
23
+ tokens
25
24
  } = _ref2;
25
+ return tokens?.rowBorderWidth || tokens?.rowBorderTopWidth ? 'solid' : 'none';
26
+ }, _ref3 => {
27
+ let {
28
+ tokens
29
+ } = _ref3;
30
+ return tokens?.rowBorderColor;
31
+ }, _ref4 => {
32
+ let {
33
+ tokens
34
+ } = _ref4;
35
+ return tokens?.rowBorderWidth || '0px';
36
+ }, _ref5 => {
37
+ let {
38
+ tokens
39
+ } = _ref5;
40
+ return tokens?.rowBorderTopWidth || tokens?.rowBorderWidth;
41
+ });
42
+ const Row = /*#__PURE__*/_react.default.forwardRef((_ref6, ref) => {
43
+ let {
44
+ children,
45
+ tokens: rowTokens
46
+ } = _ref6;
26
47
  const {
27
48
  themeTokens
28
49
  } = (0, _Table.useTableContext)();
50
+ const mergedTokens = {
51
+ ...themeTokens,
52
+ ...rowTokens
53
+ };
29
54
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(StyledTR, {
30
55
  ref: ref,
31
- rowHoverBackgroundColor: themeTokens?.rowHoverBackgroundColor,
56
+ tokens: mergedTokens,
32
57
  children: _react.default.Children.map(children, (child, index) => /*#__PURE__*/_react.default.cloneElement(child, {
33
58
  isFirstInRow: index === 0
34
59
  }))
@@ -36,6 +61,14 @@ const Row = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
36
61
  });
37
62
  Row.displayName = 'Row';
38
63
  Row.propTypes = {
64
+ tokens: _propTypes.default.shape({
65
+ borderColor: _propTypes.default.string,
66
+ topBorderWidth: _propTypes.default.string,
67
+ bottomBorderWidth: _propTypes.default.string,
68
+ leftBorderWidth: _propTypes.default.string,
69
+ rightBorderWidth: _propTypes.default.string,
70
+ rowHoverBackgroundColor: _propTypes.default.string
71
+ }),
39
72
  children: _propTypes.default.node
40
73
  };
41
74
  var _default = exports.default = Row;
@@ -1,12 +1,13 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { A11yText, Divider, selectSystemProps, Typography, useThemeTokens, useViewport, getTokensPropType } from '@telus-uds/components-base';
3
+ import { A11yText, Divider, selectSystemProps, Typography, useThemeTokens, useViewport, getTokensPropType, a11yProps } from '@telus-uds/components-base';
4
4
  import styled from 'styled-components';
5
5
  import FootnoteLink from '../Footnote/FootnoteLink';
6
6
  import getTypographyTokens from './tokens';
7
7
  import { htmlAttrs, warn } from '../utils';
8
+ import defaultDictionary from './dictionary';
8
9
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
9
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs]);
10
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, a11yProps]);
10
11
  const PriceLockupContainer = /*#__PURE__*/styled.div.withConfig({
11
12
  displayName: "PriceLockup__PriceLockupContainer",
12
13
  componentId: "components-web__sc-1x6duay-0"
@@ -16,7 +17,9 @@ const PriceLockupContainer = /*#__PURE__*/styled.div.withConfig({
16
17
  } = _ref;
17
18
  return alignItemsText;
18
19
  });
19
- const PriceContainer = /*#__PURE__*/styled.div.withConfig({
20
+ const PriceContainer = /*#__PURE__*/styled.div.attrs({
21
+ 'aria-hidden': 'true'
22
+ }).withConfig({
20
23
  displayName: "PriceLockup__PriceContainer",
21
24
  componentId: "components-web__sc-1x6duay-1"
22
25
  })(["display:flex;margin-bottom:", ";"], _ref2 => {
@@ -57,7 +60,9 @@ const BottomLinksContainer = /*#__PURE__*/styled.div.withConfig({
57
60
  } = _ref6;
58
61
  return bottomLinksMarginLeft;
59
62
  });
60
- const TopTextContainer = /*#__PURE__*/styled.div.withConfig({
63
+ const TopTextContainer = /*#__PURE__*/styled.div.attrs({
64
+ 'aria-hidden': 'true'
65
+ }).withConfig({
61
66
  displayName: "PriceLockup__TopTextContainer",
62
67
  componentId: "components-web__sc-1x6duay-5"
63
68
  })(["margin-bottom:", ";"], _ref7 => {
@@ -158,6 +163,8 @@ const PriceLockup = /*#__PURE__*/React.forwardRef((_ref16, ref) => {
158
163
  a11yText,
159
164
  tokens: priceLockupTokens,
160
165
  variant = {},
166
+ copy = 'en',
167
+ dictionary = defaultDictionary,
161
168
  ...rest
162
169
  } = _ref16;
163
170
  const viewport = useViewport();
@@ -270,10 +277,28 @@ const PriceLockup = /*#__PURE__*/React.forwardRef((_ref16, ref) => {
270
277
  if (strikeThrough && !a11yText) {
271
278
  warn('PriceLockup', 'a11yText must be provided with strikethrough pricing');
272
279
  }
280
+ const getAriaContent = () => {
281
+ const {
282
+ dictionaryPrice = price,
283
+ priceWithCents,
284
+ rate
285
+ } = dictionary[copy];
286
+ let ariaLabel = hasCents ? priceWithCents.replace('%{amount}', amount).replace('%{cents}', cents).replace('%{currency}', currencySymbol) : dictionaryPrice.replace('%{amount}', amount).replace('%{currency}', currencySymbol);
287
+ if (!ariaLabel.includes(currencySymbol)) {
288
+ ariaLabel = `${ariaLabel} ${currencySymbol}`;
289
+ }
290
+ if (rateText) {
291
+ ariaLabel += ` ${rate.replace('%{rateText}', rateText.replace('/', ''))}`;
292
+ }
293
+ return ariaLabel;
294
+ };
295
+ const ariaLabel = selectProps(rest)['aria-label'] ?? getAriaContent();
273
296
  return /*#__PURE__*/_jsxs(PriceLockupContainer, {
274
297
  ...selectProps(rest),
275
298
  alignItemsText: alignItemsText,
276
299
  ref: ref,
300
+ role: "group",
301
+ "aria-label": ariaLabel,
277
302
  children: [topText && /*#__PURE__*/_jsx(TopTextContainer, {
278
303
  topTextMarginBottom: `${topTextMarginBottom}px`,
279
304
  children: renderTypography(topText, typographyTokens.topText)
@@ -287,6 +312,13 @@ const PriceLockup = /*#__PURE__*/React.forwardRef((_ref16, ref) => {
287
312
  });
288
313
  });
289
314
  PriceLockup.displayName = 'PriceLockup';
315
+
316
+ // If a language dictionary entry is provided, it must contain every key
317
+ const dictionaryContentShape = PropTypes.shape({
318
+ price: PropTypes.string.isRequired,
319
+ priceWithCents: PropTypes.string.isRequired,
320
+ rate: PropTypes.string.isRequired
321
+ });
290
322
  PriceLockup.propTypes = {
291
323
  ...selectedSystemPropTypes,
292
324
  /**
@@ -341,6 +373,16 @@ PriceLockup.propTypes = {
341
373
  * **Note:** a11yText will override strikethrough price, so it must include price (ie. "was 50 dollars per month")
342
374
  */
343
375
  a11yText: PropTypes.string,
376
+ /**
377
+ * Select English or French copy for the accessible label.
378
+ */
379
+ copy: PropTypes.oneOf(['en', 'fr']),
380
+ /* Custom dictionary containing the labels
381
+ */
382
+ dictionary: PropTypes.shape({
383
+ en: dictionaryContentShape,
384
+ fr: dictionaryContentShape
385
+ }),
344
386
  /**
345
387
  * `PriceLockup` tokens
346
388
  */
@@ -0,0 +1,12 @@
1
+ export default {
2
+ en: {
3
+ price: '%{amount} %{currency}',
4
+ priceWithCents: '%{amount} %{currency} and %{cents} cents',
5
+ rate: 'per %{rateText}'
6
+ },
7
+ fr: {
8
+ price: '%{amount} %{currency}',
9
+ priceWithCents: '%{amount} %{currency} et %{cents} centimes',
10
+ rate: 'par %{rateText}'
11
+ }
12
+ };
@@ -25,17 +25,20 @@ const sharedStyles = /*#__PURE__*/css(["", ""], _ref2 => {
25
25
  cellPaddingRight,
26
26
  cellPaddingBottom,
27
27
  cellPaddingLeft,
28
+ cellBorderWidth,
29
+ cellBorderColor,
30
+ cellBorderTopWidth,
28
31
  cellMinWidth,
29
32
  cellBackground,
30
33
  cellStickyShadow,
31
34
  stickyBackgroundColor,
32
35
  type
33
36
  } = _ref2;
34
- return css(["text-align:", ";min-width:", "px;padding:", "px ", "px ", "px ", "px;background-color:", ";", ";"], align, cellMinWidth, cellPaddingTop, cellPaddingRight, cellPaddingBottom, cellPaddingLeft, cellBackground, isSticky && stickyStyles({
37
+ return css(["text-align:", ";min-width:", "px;padding:", "px ", "px ", "px ", "px;background-color:", ";", ";border-style:", ";border-color:", ";border-width:", ";border-top-width:", ";"], align, cellMinWidth, cellPaddingTop, cellPaddingRight, cellPaddingBottom, cellPaddingLeft, cellBackground, isSticky && stickyStyles({
35
38
  type,
36
39
  cellStickyShadow,
37
40
  stickyBackgroundColor
38
- }));
41
+ }), cellBorderWidth || cellBorderTopWidth ? 'solid' : 'none', cellBorderColor, cellBorderWidth, cellBorderTopWidth || cellBorderWidth);
39
42
  });
40
43
  const createStyledCell = htmlElement => styled[htmlElement].withConfig({
41
44
  displayName: "Cell__createStyledCell",
@@ -84,6 +87,9 @@ const Cell = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
84
87
  cellPaddingRight,
85
88
  cellPaddingLeft,
86
89
  cellPaddingBottom,
90
+ cellBorderWidth,
91
+ cellBorderColor,
92
+ cellBorderTopWidth,
87
93
  fontName,
88
94
  fontWeight,
89
95
  fontSize,
@@ -107,6 +113,9 @@ const Cell = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
107
113
  cellPaddingRight,
108
114
  cellPaddingLeft,
109
115
  cellPaddingBottom,
116
+ cellBorderWidth,
117
+ cellBorderColor,
118
+ cellBorderTopWidth,
110
119
  stickyBackgroundColor,
111
120
  cellBoxShadowColor,
112
121
  display,
@@ -6,22 +6,47 @@ import { jsx as _jsx } from "react/jsx-runtime";
6
6
  const StyledTR = /*#__PURE__*/styled.tr.withConfig({
7
7
  displayName: "Row__StyledTR",
8
8
  componentId: "components-web__sc-6pbb9a-0"
9
- })(["&:hover{background-color:", " !important;}"], _ref => {
9
+ })(["&:hover{background-color:", " !important;}border-style:", ";border-color:", ";border-width:", ";border-top-width:", ";"], _ref => {
10
10
  let {
11
- rowHoverBackgroundColor
11
+ tokens
12
12
  } = _ref;
13
- return rowHoverBackgroundColor;
14
- });
15
- const Row = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
13
+ return tokens?.rowHoverBackgroundColor;
14
+ }, _ref2 => {
16
15
  let {
17
- children
16
+ tokens
18
17
  } = _ref2;
18
+ return tokens?.rowBorderWidth || tokens?.rowBorderTopWidth ? 'solid' : 'none';
19
+ }, _ref3 => {
20
+ let {
21
+ tokens
22
+ } = _ref3;
23
+ return tokens?.rowBorderColor;
24
+ }, _ref4 => {
25
+ let {
26
+ tokens
27
+ } = _ref4;
28
+ return tokens?.rowBorderWidth || '0px';
29
+ }, _ref5 => {
30
+ let {
31
+ tokens
32
+ } = _ref5;
33
+ return tokens?.rowBorderTopWidth || tokens?.rowBorderWidth;
34
+ });
35
+ const Row = /*#__PURE__*/React.forwardRef((_ref6, ref) => {
36
+ let {
37
+ children,
38
+ tokens: rowTokens
39
+ } = _ref6;
19
40
  const {
20
41
  themeTokens
21
42
  } = useTableContext();
43
+ const mergedTokens = {
44
+ ...themeTokens,
45
+ ...rowTokens
46
+ };
22
47
  return /*#__PURE__*/_jsx(StyledTR, {
23
48
  ref: ref,
24
- rowHoverBackgroundColor: themeTokens?.rowHoverBackgroundColor,
49
+ tokens: mergedTokens,
25
50
  children: React.Children.map(children, (child, index) => /*#__PURE__*/React.cloneElement(child, {
26
51
  isFirstInRow: index === 0
27
52
  }))
@@ -29,6 +54,14 @@ const Row = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
29
54
  });
30
55
  Row.displayName = 'Row';
31
56
  Row.propTypes = {
57
+ tokens: PropTypes.shape({
58
+ borderColor: PropTypes.string,
59
+ topBorderWidth: PropTypes.string,
60
+ bottomBorderWidth: PropTypes.string,
61
+ leftBorderWidth: PropTypes.string,
62
+ rightBorderWidth: PropTypes.string,
63
+ rowHoverBackgroundColor: PropTypes.string
64
+ }),
32
65
  children: PropTypes.node
33
66
  };
34
67
  export default Row;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  ],
6
6
  "dependencies": {
7
7
  "@gorhom/portal": "^1.0.14",
8
- "@telus-uds/components-base": "^3.0.0",
8
+ "@telus-uds/components-base": "^3.1.0",
9
9
  "@telus-uds/system-constants": "^3.0.0",
10
10
  "fscreen": "^1.2.0",
11
11
  "lodash.omit": "^4.5.0",
@@ -13,7 +13,7 @@
13
13
  "react-dates": "^21.8.0",
14
14
  "react-helmet-async": "^1.3.0",
15
15
  "react-moment-proptypes": "^1.8.1",
16
- "@telus-uds/system-theme-tokens": "^4.0.0",
16
+ "@telus-uds/system-theme-tokens": "^4.1.0",
17
17
  "prop-types": "^15.7.2",
18
18
  "lodash.throttle": "^4.1.1",
19
19
  "react-youtube": "^10.1.0",
@@ -82,5 +82,5 @@
82
82
  "skip": true
83
83
  },
84
84
  "types": "types/index.d.ts",
85
- "version": "4.0.0"
85
+ "version": "4.1.0"
86
86
  }
@@ -7,14 +7,16 @@ import {
7
7
  Typography,
8
8
  useThemeTokens,
9
9
  useViewport,
10
- getTokensPropType
10
+ getTokensPropType,
11
+ a11yProps
11
12
  } from '@telus-uds/components-base'
12
13
  import styled from 'styled-components'
13
14
  import FootnoteLink from '../Footnote/FootnoteLink'
14
15
  import getTypographyTokens from './tokens'
15
16
  import { htmlAttrs, warn } from '../utils'
17
+ import defaultDictionary from './dictionary'
16
18
 
17
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
19
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, a11yProps])
18
20
 
19
21
  const PriceLockupContainer = styled.div`
20
22
  align-items: ${({ alignItemsText }) => alignItemsText};
@@ -22,12 +24,12 @@ const PriceLockupContainer = styled.div`
22
24
  flex-direction: column;
23
25
  width: fit-content;
24
26
  `
25
-
26
- const PriceContainer = styled.div`
27
+ const PriceContainer = styled.div.attrs({
28
+ 'aria-hidden': 'true'
29
+ })`
27
30
  display: flex;
28
31
  margin-bottom: ${({ priceMarginBottom }) => priceMarginBottom};
29
32
  `
30
-
31
33
  const FootnoteContainer = styled.div`
32
34
  display: flex;
33
35
  margin-top: ${({ footnoteMarginTop }) => footnoteMarginTop};
@@ -43,7 +45,9 @@ const BottomLinksContainer = styled.div`
43
45
  margin-left: ${({ bottomLinksMarginLeft }) => bottomLinksMarginLeft};
44
46
  `
45
47
 
46
- const TopTextContainer = styled.div`
48
+ const TopTextContainer = styled.div.attrs({
49
+ 'aria-hidden': 'true'
50
+ })`
47
51
  margin-bottom: ${({ topTextMarginBottom }) => topTextMarginBottom};
48
52
  `
49
53
 
@@ -119,6 +123,8 @@ const PriceLockup = React.forwardRef(
119
123
  a11yText,
120
124
  tokens: priceLockupTokens,
121
125
  variant = {},
126
+ copy = 'en',
127
+ dictionary = defaultDictionary,
122
128
  ...rest
123
129
  },
124
130
  ref
@@ -245,8 +251,35 @@ const PriceLockup = React.forwardRef(
245
251
  warn('PriceLockup', 'a11yText must be provided with strikethrough pricing')
246
252
  }
247
253
 
254
+ const getAriaContent = () => {
255
+ const { dictionaryPrice = price, priceWithCents, rate } = dictionary[copy]
256
+ let ariaLabel = hasCents
257
+ ? priceWithCents
258
+ .replace('%{amount}', amount)
259
+ .replace('%{cents}', cents)
260
+ .replace('%{currency}', currencySymbol)
261
+ : dictionaryPrice.replace('%{amount}', amount).replace('%{currency}', currencySymbol)
262
+
263
+ if (!ariaLabel.includes(currencySymbol)) {
264
+ ariaLabel = `${ariaLabel} ${currencySymbol}`
265
+ }
266
+
267
+ if (rateText) {
268
+ ariaLabel += ` ${rate.replace('%{rateText}', rateText.replace('/', ''))}`
269
+ }
270
+ return ariaLabel
271
+ }
272
+
273
+ const ariaLabel = selectProps(rest)['aria-label'] ?? getAriaContent()
274
+
248
275
  return (
249
- <PriceLockupContainer {...selectProps(rest)} alignItemsText={alignItemsText} ref={ref}>
276
+ <PriceLockupContainer
277
+ {...selectProps(rest)}
278
+ alignItemsText={alignItemsText}
279
+ ref={ref}
280
+ role="group"
281
+ aria-label={ariaLabel}
282
+ >
250
283
  {topText && (
251
284
  <TopTextContainer topTextMarginBottom={`${topTextMarginBottom}px`}>
252
285
  {renderTypography(topText, typographyTokens.topText)}
@@ -268,6 +301,13 @@ const PriceLockup = React.forwardRef(
268
301
 
269
302
  PriceLockup.displayName = 'PriceLockup'
270
303
 
304
+ // If a language dictionary entry is provided, it must contain every key
305
+ const dictionaryContentShape = PropTypes.shape({
306
+ price: PropTypes.string.isRequired,
307
+ priceWithCents: PropTypes.string.isRequired,
308
+ rate: PropTypes.string.isRequired
309
+ })
310
+
271
311
  PriceLockup.propTypes = {
272
312
  ...selectedSystemPropTypes,
273
313
  /**
@@ -322,6 +362,16 @@ PriceLockup.propTypes = {
322
362
  * **Note:** a11yText will override strikethrough price, so it must include price (ie. "was 50 dollars per month")
323
363
  */
324
364
  a11yText: PropTypes.string,
365
+ /**
366
+ * Select English or French copy for the accessible label.
367
+ */
368
+ copy: PropTypes.oneOf(['en', 'fr']),
369
+ /* Custom dictionary containing the labels
370
+ */
371
+ dictionary: PropTypes.shape({
372
+ en: dictionaryContentShape,
373
+ fr: dictionaryContentShape
374
+ }),
325
375
  /**
326
376
  * `PriceLockup` tokens
327
377
  */
@@ -0,0 +1,12 @@
1
+ export default {
2
+ en: {
3
+ price: '%{amount} %{currency}',
4
+ priceWithCents: '%{amount} %{currency} and %{cents} cents',
5
+ rate: 'per %{rateText}'
6
+ },
7
+ fr: {
8
+ price: '%{amount} %{currency}',
9
+ priceWithCents: '%{amount} %{currency} et %{cents} centimes',
10
+ rate: 'par %{rateText}'
11
+ }
12
+ }
@@ -39,6 +39,9 @@ const sharedStyles = css`
39
39
  cellPaddingRight,
40
40
  cellPaddingBottom,
41
41
  cellPaddingLeft,
42
+ cellBorderWidth,
43
+ cellBorderColor,
44
+ cellBorderTopWidth,
42
45
  cellMinWidth,
43
46
  cellBackground,
44
47
  cellStickyShadow,
@@ -50,6 +53,10 @@ const sharedStyles = css`
50
53
  padding: ${cellPaddingTop}px ${cellPaddingRight}px ${cellPaddingBottom}px ${cellPaddingLeft}px;
51
54
  background-color: ${cellBackground};
52
55
  ${isSticky && stickyStyles({ type, cellStickyShadow, stickyBackgroundColor })};
56
+ border-style: ${cellBorderWidth || cellBorderTopWidth ? 'solid' : 'none'};
57
+ border-color: ${cellBorderColor};
58
+ border-width: ${cellBorderWidth};
59
+ border-top-width: ${cellBorderTopWidth || cellBorderWidth};
53
60
  `}
54
61
  `
55
62
  const createStyledCell = (htmlElement) => styled[htmlElement]`
@@ -88,6 +95,9 @@ const Cell = React.forwardRef(
88
95
  cellPaddingRight,
89
96
  cellPaddingLeft,
90
97
  cellPaddingBottom,
98
+ cellBorderWidth,
99
+ cellBorderColor,
100
+ cellBorderTopWidth,
91
101
  fontName,
92
102
  fontWeight,
93
103
  fontSize,
@@ -107,6 +117,9 @@ const Cell = React.forwardRef(
107
117
  cellPaddingRight,
108
118
  cellPaddingLeft,
109
119
  cellPaddingBottom,
120
+ cellBorderWidth,
121
+ cellBorderColor,
122
+ cellBorderTopWidth,
110
123
  stickyBackgroundColor,
111
124
  cellBoxShadowColor,
112
125
  display,
package/src/Table/Row.jsx CHANGED
@@ -1,20 +1,25 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
-
4
3
  import styled from 'styled-components'
5
4
  import { useTableContext } from './Table'
6
5
 
7
6
  const StyledTR = styled.tr`
8
7
  &:hover {
9
- background-color: ${({ rowHoverBackgroundColor }) => rowHoverBackgroundColor} !important;
8
+ background-color: ${({ tokens }) => tokens?.rowHoverBackgroundColor} !important;
10
9
  }
10
+ border-style: ${({ tokens }) =>
11
+ tokens?.rowBorderWidth || tokens?.rowBorderTopWidth ? 'solid' : 'none'};
12
+ border-color: ${({ tokens }) => tokens?.rowBorderColor};
13
+ border-width: ${({ tokens }) => tokens?.rowBorderWidth || '0px'};
14
+ border-top-width: ${({ tokens }) => tokens?.rowBorderTopWidth || tokens?.rowBorderWidth};
11
15
  `
12
16
 
13
- const Row = React.forwardRef(({ children }, ref) => {
17
+ const Row = React.forwardRef(({ children, tokens: rowTokens }, ref) => {
14
18
  const { themeTokens } = useTableContext()
19
+ const mergedTokens = { ...themeTokens, ...rowTokens }
15
20
 
16
21
  return (
17
- <StyledTR ref={ref} rowHoverBackgroundColor={themeTokens?.rowHoverBackgroundColor}>
22
+ <StyledTR ref={ref} tokens={mergedTokens}>
18
23
  {React.Children.map(children, (child, index) =>
19
24
  React.cloneElement(child, { isFirstInRow: index === 0 })
20
25
  )}
@@ -25,6 +30,14 @@ const Row = React.forwardRef(({ children }, ref) => {
25
30
  Row.displayName = 'Row'
26
31
 
27
32
  Row.propTypes = {
33
+ tokens: PropTypes.shape({
34
+ borderColor: PropTypes.string,
35
+ topBorderWidth: PropTypes.string,
36
+ bottomBorderWidth: PropTypes.string,
37
+ leftBorderWidth: PropTypes.string,
38
+ rightBorderWidth: PropTypes.string,
39
+ rowHoverBackgroundColor: PropTypes.string
40
+ }),
28
41
  children: PropTypes.node
29
42
  }
30
43
 
@@ -0,0 +1,67 @@
1
+ // TODO: Duplicate of the one in components-base. TS module resolution doesn't seem to be working from web -> base.
2
+ import type { ComponentType, ElementType } from 'react'
3
+
4
+ type ListboxTokens = {
5
+ groupBorderRadius?: number | string
6
+ groupBorderWidth?: number | string
7
+ groupFontSize?: number
8
+ groupFontName?: string
9
+ groupFontWeight?: string
10
+ groupColor?: string
11
+ groupBorderColor?: string
12
+ groupBackgroundColor?: string
13
+ groupPaddingTop?: number
14
+ groupPaddingBottom?: number
15
+ groupPaddingLeft?: number
16
+ groupPaddingRight?: number
17
+ groupIcon?: string
18
+ itemDisplay?: boolean
19
+ itemFontName?: string
20
+ itemFontWeight?: string
21
+ itemFontSize?: number
22
+ itemPaddingTop?: number
23
+ itemPaddingBottom?: number
24
+ itemPaddingLeft?: number
25
+ itemPaddingRight?: number
26
+ itemColor?: string
27
+ itemBackgroundColor?: string
28
+ itemBorderLeftColor?: string
29
+ itemBorderLeftWidth?: number | string
30
+ itemBorderWidth?: number | string
31
+ itemTextDecoration?: string
32
+ itemOutline?: number | string
33
+ shadow?: string
34
+ itemBorderRightColor?: string
35
+ itemBorderBottomColor?: string
36
+ itemBorderTopColor?: string
37
+ itemBorderRightWidth?: number | string
38
+ itemBorderBottomWidth?: number | string
39
+ itemBorderTopWidth?: number | string
40
+ itemBorderRadius?: number | string
41
+ minWidth?: number
42
+ minHeight?: number
43
+ itemHeight?: number
44
+ groupHeight?: number
45
+ lineHeight?: number
46
+ }
47
+
48
+ type ListboxItems = {
49
+ label: string
50
+ href: string
51
+ tokens?: ListboxTokens
52
+ }
53
+
54
+ export interface ListboxProps {
55
+ items: ListboxItems[]
56
+ firstItemRef?: object
57
+ parentRef?: object
58
+ LinkRouter?: ElementType
59
+ linkRouterProps?: object
60
+ tokens?: ListboxTokens
61
+ selectedId?: string
62
+ onClose?: () => void
63
+ }
64
+
65
+ declare const Listbox: ComponentType<ListboxProps>
66
+
67
+ export default Listbox
package/types/index.d.ts CHANGED
@@ -42,6 +42,9 @@ declare const ExpandCollapseMini: ComponentType<Props>
42
42
  export { default as FileUpload } from './FileUpload'
43
43
  export type { FileUploadProps, FileUploadTokens } from './FileUpload'
44
44
 
45
+ // Listbox Exports
46
+ export { default as Listbox } from './Listbox'
47
+
45
48
  declare const Footnote: ComponentType<Props> & {
46
49
  Link: ComponentType<Props>
47
50
  }