@telus-uds/components-base 1.70.0 → 1.72.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 (96) hide show
  1. package/CHANGELOG.md +30 -2
  2. package/jest.setup.js +7 -0
  3. package/lib/Autocomplete/Autocomplete.js +3 -13
  4. package/lib/Card/Card.js +68 -7
  5. package/lib/Card/PressableCardBase.js +2 -0
  6. package/lib/ColourToggle/ColourBubble.js +135 -0
  7. package/lib/ColourToggle/ColourToggle.js +101 -0
  8. package/lib/ColourToggle/index.js +10 -0
  9. package/lib/FlexGrid/Col/Col.js +50 -64
  10. package/lib/FlexGrid/FlexGrid.js +37 -40
  11. package/lib/FlexGrid/Row/Row.js +43 -44
  12. package/lib/Icon/IconText.js +9 -2
  13. package/lib/Link/LinkBase.js +10 -3
  14. package/lib/Modal/ModalContent.js +4 -6
  15. package/lib/OrderedList/Item.js +180 -0
  16. package/lib/OrderedList/ItemBase.js +48 -0
  17. package/lib/OrderedList/OrderedList.js +71 -0
  18. package/lib/OrderedList/OrderedListBase.js +47 -0
  19. package/lib/OrderedList/index.js +10 -0
  20. package/lib/index.js +16 -0
  21. package/lib/utils/ssr-media-query/create-stylesheet/create-stylesheet-mobile.js +56 -0
  22. package/lib/utils/ssr-media-query/create-stylesheet/index.android.js +10 -0
  23. package/lib/utils/ssr-media-query/create-stylesheet/index.ios.js +10 -0
  24. package/lib/utils/ssr-media-query/create-stylesheet/index.js +44 -0
  25. package/lib/utils/ssr-media-query/utils/inject.js +13 -0
  26. package/lib-module/Autocomplete/Autocomplete.js +3 -13
  27. package/lib-module/Card/Card.js +71 -8
  28. package/lib-module/Card/PressableCardBase.js +2 -0
  29. package/lib-module/ColourToggle/ColourBubble.js +125 -0
  30. package/lib-module/ColourToggle/ColourToggle.js +92 -0
  31. package/lib-module/ColourToggle/index.js +2 -0
  32. package/lib-module/FlexGrid/Col/Col.js +51 -65
  33. package/lib-module/FlexGrid/FlexGrid.js +38 -41
  34. package/lib-module/FlexGrid/Row/Row.js +44 -45
  35. package/lib-module/Icon/IconText.js +9 -2
  36. package/lib-module/Link/LinkBase.js +10 -3
  37. package/lib-module/Modal/ModalContent.js +4 -6
  38. package/lib-module/OrderedList/Item.js +171 -0
  39. package/lib-module/OrderedList/ItemBase.js +37 -0
  40. package/lib-module/OrderedList/OrderedList.js +61 -0
  41. package/lib-module/OrderedList/OrderedListBase.js +36 -0
  42. package/lib-module/OrderedList/index.js +2 -0
  43. package/lib-module/index.js +2 -0
  44. package/lib-module/utils/ssr-media-query/create-stylesheet/create-stylesheet-mobile.js +48 -0
  45. package/lib-module/utils/ssr-media-query/create-stylesheet/index.android.js +2 -0
  46. package/lib-module/utils/ssr-media-query/create-stylesheet/index.ios.js +2 -0
  47. package/lib-module/utils/ssr-media-query/create-stylesheet/index.js +36 -0
  48. package/lib-module/utils/ssr-media-query/utils/inject.js +13 -0
  49. package/package.json +2 -2
  50. package/src/Autocomplete/Autocomplete.jsx +14 -21
  51. package/src/Card/Card.jsx +73 -11
  52. package/src/Card/PressableCardBase.jsx +2 -0
  53. package/src/ColourToggle/ColourBubble.jsx +111 -0
  54. package/src/ColourToggle/ColourToggle.jsx +83 -0
  55. package/src/ColourToggle/index.js +3 -0
  56. package/src/FlexGrid/Col/Col.jsx +48 -80
  57. package/src/FlexGrid/FlexGrid.jsx +36 -44
  58. package/src/FlexGrid/Row/Row.jsx +38 -56
  59. package/src/Icon/IconText.jsx +11 -1
  60. package/src/Link/ChevronLink.jsx +1 -0
  61. package/src/Link/LinkBase.jsx +16 -6
  62. package/src/Modal/ModalContent.jsx +4 -6
  63. package/src/OrderedList/Item.jsx +152 -0
  64. package/src/OrderedList/ItemBase.jsx +31 -0
  65. package/src/OrderedList/OrderedList.jsx +61 -0
  66. package/src/OrderedList/OrderedListBase.jsx +33 -0
  67. package/src/OrderedList/index.js +3 -0
  68. package/src/index.js +2 -0
  69. package/src/utils/ssr-media-query/create-stylesheet/create-stylesheet-mobile.js +41 -0
  70. package/src/utils/ssr-media-query/create-stylesheet/index.android.js +3 -0
  71. package/src/utils/ssr-media-query/create-stylesheet/index.ios.js +3 -0
  72. package/src/utils/ssr-media-query/create-stylesheet/index.js +33 -0
  73. package/src/utils/ssr-media-query/utils/inject.js +13 -0
  74. package/types/Badge.d.ts +28 -0
  75. package/types/Box.d.ts +52 -0
  76. package/types/ChevronLink.d.ts +47 -0
  77. package/types/Common.d.ts +106 -0
  78. package/types/Divider.d.ts +19 -0
  79. package/types/ExpandCollapse.d.ts +65 -0
  80. package/types/HorizontalScrollButton.d.ts +16 -0
  81. package/types/Icon.d.ts +21 -0
  82. package/types/Link.d.ts +48 -0
  83. package/types/List.d.ts +48 -0
  84. package/types/Search.d.ts +38 -0
  85. package/types/Select.d.ts +57 -0
  86. package/types/Spacer.d.ts +5 -0
  87. package/types/StackView.d.ts +28 -0
  88. package/types/Tabs.d.ts +46 -0
  89. package/types/TextButton.d.ts +11 -0
  90. package/types/ToggleSwitch.d.ts +54 -0
  91. package/types/ToolTip.d.ts +40 -0
  92. package/types/Typography.d.ts +39 -0
  93. package/types/index.d.ts +62 -0
  94. package/lib/utils/ssr-media-query/create-stylesheet.js +0 -76
  95. package/lib-module/utils/ssr-media-query/create-stylesheet.js +0 -68
  96. package/src/utils/ssr-media-query/create-stylesheet.js +0 -61
@@ -0,0 +1,61 @@
1
+ import React, { forwardRef, useMemo } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { getTokensPropType, htmlAttrs, selectSystemProps, variantProp, viewProps } from '../utils';
4
+ import OrderedListBase from './OrderedListBase';
5
+ import Item from './Item';
6
+ import { jsx as _jsx } from "react/jsx-runtime";
7
+ const [selectProps, selectedSystemPropsTypes] = selectSystemProps([htmlAttrs, viewProps]);
8
+ const getChildrenWithParentVariants = (variant, children, start) => {
9
+ if (variant) return children.map((child, i) => {
10
+ var _child$props;
11
+ const existingChildVariants = ((_child$props = child.props) === null || _child$props === void 0 ? void 0 : _child$props.variant) ?? {};
12
+ return {
13
+ ...child,
14
+ props: {
15
+ ...child.props,
16
+ index: start + i,
17
+ isLastChild: i === children.length - 1,
18
+ variant: {
19
+ ...existingChildVariants,
20
+ ...variant
21
+ }
22
+ }
23
+ };
24
+ });
25
+ return children;
26
+ };
27
+ const OrderedList = /*#__PURE__*/forwardRef((_ref, ref) => {
28
+ let {
29
+ children,
30
+ start,
31
+ variant,
32
+ ...rest
33
+ } = _ref;
34
+ const childrenWithParentVariants = useMemo(() => getChildrenWithParentVariants(variant, children, start), [children, variant, start]);
35
+ return /*#__PURE__*/_jsx(OrderedListBase, {
36
+ ref: ref,
37
+ ...selectProps(rest),
38
+ children: childrenWithParentVariants
39
+ });
40
+ });
41
+ OrderedList.propTypes = {
42
+ ...selectedSystemPropsTypes,
43
+ tokens: getTokensPropType('OrderedList'),
44
+ /**
45
+ * A list of ordered items wrapped in `OrderedList.Item`.
46
+ */
47
+ children: PropTypes.node.isRequired,
48
+ /**
49
+ * The position to start the list with.
50
+ */
51
+ start: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
52
+ variant: variantProp.propType
53
+ };
54
+ OrderedList.defaultProps = {
55
+ start: 1,
56
+ tokens: {},
57
+ variant: {}
58
+ };
59
+ OrderedList.displayName = 'OrderedList';
60
+ OrderedList.Item = Item;
61
+ export default OrderedList;
@@ -0,0 +1,36 @@
1
+ /* eslint-disable react-native-a11y/has-valid-accessibility-role */
2
+ import React, { forwardRef } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import View from "react-native-web/dist/exports/View";
5
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
6
+ import ItemBase from './ItemBase';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ const OrderedListBase = /*#__PURE__*/forwardRef((_ref, ref) => {
9
+ let {
10
+ children,
11
+ ...rest
12
+ } = _ref;
13
+ return /*#__PURE__*/_jsx(View, {
14
+ accessibilityRole: "list",
15
+ ref: ref,
16
+ style: staticStyles.container,
17
+ ...rest,
18
+ children: children
19
+ });
20
+ });
21
+ OrderedListBase.propTypes = {
22
+ /**
23
+ * A list of ordered items wrapped in `OrderedListBase.Item`.
24
+ */
25
+ children: PropTypes.node.isRequired
26
+ };
27
+ OrderedListBase.displayName = 'OrderedList';
28
+ OrderedListBase.Item = ItemBase;
29
+ export default OrderedListBase;
30
+ const staticStyles = StyleSheet.create({
31
+ container: {
32
+ flexDirection: 'column',
33
+ margin: 0,
34
+ padding: 0
35
+ }
36
+ });
@@ -0,0 +1,2 @@
1
+ import OrderedList from './OrderedList';
2
+ export default OrderedList;
@@ -11,6 +11,7 @@ export { default as Checkbox } from './Checkbox';
11
11
  export * from './Checkbox';
12
12
  export { default as CheckboxCard } from './CheckboxCard';
13
13
  export { default as CheckboxCardGroup } from './CheckboxCardGroup';
14
+ export { default as ColourToggle } from './ColourToggle';
14
15
  export { default as Divider } from './Divider';
15
16
  export { default as ExpandCollapse, Accordion } from './ExpandCollapse';
16
17
  export { default as Feedback } from './Feedback';
@@ -28,6 +29,7 @@ export { default as List, ListItem, ListBase } from './List';
28
29
  export { default as Modal } from './Modal';
29
30
  export { default as MultiSelectFilter } from './MultiSelectFilter';
30
31
  export { default as Notification } from './Notification';
32
+ export { default as OrderedList } from './OrderedList';
31
33
  export { default as Pagination } from './Pagination';
32
34
  export { default as Progress } from './Progress';
33
35
  export { default as QuickLinks } from './QuickLinks';
@@ -0,0 +1,48 @@
1
+ import mediaQuery from 'css-mediaquery';
2
+ import Dimensions from "react-native-web/dist/exports/Dimensions";
3
+ import { isMediaOrPseudo, isMedia } from '../utils/common';
4
+ const createStyleSheet = stylesWithQuery => {
5
+ if (!stylesWithQuery) return {
6
+ ids: {},
7
+ styles: {},
8
+ fullStyles: {}
9
+ };
10
+ let cleanStyles;
11
+ const ids = {};
12
+ Object.keys(stylesWithQuery).forEach(key => {
13
+ if (!(stylesWithQuery !== null && stylesWithQuery !== void 0 && stylesWithQuery[key])) return;
14
+ const mediaQueriesAndPseudoClasses = Object.keys(stylesWithQuery[key]).filter(isMediaOrPseudo);
15
+ cleanStyles = JSON.parse(JSON.stringify(stylesWithQuery));
16
+ mediaQueriesAndPseudoClasses.forEach(str => {
17
+ if (isMedia(str)) {
18
+ const mqStr = str.replace('@media', '');
19
+ const {
20
+ width,
21
+ height
22
+ } = Dimensions.get('window');
23
+ const isMatchingMediaQuery = mediaQuery.match(mqStr, {
24
+ width,
25
+ height,
26
+ orientation: width > height ? 'landscape' : 'portrait',
27
+ 'aspect-ratio': width / height
28
+ });
29
+ if (isMatchingMediaQuery) {
30
+ cleanStyles = {
31
+ ...cleanStyles,
32
+ [key]: {
33
+ ...cleanStyles[key],
34
+ ...stylesWithQuery[key][str]
35
+ }
36
+ };
37
+ }
38
+ }
39
+ delete cleanStyles[key][str];
40
+ });
41
+ });
42
+ return {
43
+ ids,
44
+ styles: cleanStyles,
45
+ fullStyles: stylesWithQuery
46
+ };
47
+ };
48
+ export default createStyleSheet;
@@ -0,0 +1,2 @@
1
+ import createStyleSheet from './create-stylesheet-mobile';
2
+ export default createStyleSheet;
@@ -0,0 +1,2 @@
1
+ import createStyleSheet from './create-stylesheet-mobile';
2
+ export default createStyleSheet;
@@ -0,0 +1,36 @@
1
+ import { addCss } from '../utils/inject';
2
+ import createDeclarationBlock from '../utils/create-declaration-block';
3
+ import hash from '../hash';
4
+ import { isMediaOrPseudo, deepClone, createCssRule } from '../utils/common';
5
+ const createStyleSheet = stylesWithQuery => {
6
+ if (!stylesWithQuery) return {
7
+ ids: {},
8
+ styles: {},
9
+ fullStyles: {}
10
+ };
11
+ let cleanStyles;
12
+ let ids = {};
13
+ Object.keys(stylesWithQuery).forEach(key => {
14
+ if (!(stylesWithQuery !== null && stylesWithQuery !== void 0 && stylesWithQuery[key])) return;
15
+ const mediaQueriesAndPseudoClasses = Object.keys(stylesWithQuery[key]).filter(isMediaOrPseudo);
16
+ cleanStyles = deepClone(stylesWithQuery);
17
+ mediaQueriesAndPseudoClasses.forEach(query => {
18
+ var _ids;
19
+ const css = createDeclarationBlock(stylesWithQuery[key][query]);
20
+ const stringHash = `rnmq-${hash(`${key}${query}${css}`)}`;
21
+ const rule = createCssRule(query, stringHash, css);
22
+ addCss(`${stringHash}`, rule);
23
+ delete cleanStyles[key][query];
24
+ ids = {
25
+ ...ids,
26
+ [key]: `${(_ids = ids) !== null && _ids !== void 0 && _ids[key] ? `${ids[key]} ` : ''}${stringHash}`
27
+ };
28
+ });
29
+ });
30
+ return {
31
+ ids,
32
+ styles: cleanStyles,
33
+ fullStyles: stylesWithQuery
34
+ };
35
+ };
36
+ export default createStyleSheet;
@@ -1,6 +1,16 @@
1
1
  import React from 'react';
2
2
  import Platform from "react-native-web/dist/exports/Platform";
3
3
  const rules = {};
4
+ let styleSheet;
5
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
6
+ styleSheet = (() => {
7
+ const style = document.createElement('style');
8
+ style.id = 'RNMQCSS';
9
+ style.appendChild(document.createTextNode(''));
10
+ document.head.appendChild(style);
11
+ return style.sheet;
12
+ })();
13
+ }
4
14
  export const hasCss = (id, text) => {
5
15
  var _rules$id$text, _rules$id$text$includ;
6
16
  return !!rules[id] && !!((_rules$id$text = rules[id].text) !== null && _rules$id$text !== void 0 && (_rules$id$text$includ = _rules$id$text.includes) !== null && _rules$id$text$includ !== void 0 && _rules$id$text$includ.call(_rules$id$text, text));
@@ -10,6 +20,9 @@ export const addCss = (id, text) => {
10
20
  var _rules$id;
11
21
  rules[id] = (rules === null || rules === void 0 ? void 0 : rules[id]) || {};
12
22
  rules[id].text = (((_rules$id = rules[id]) === null || _rules$id === void 0 ? void 0 : _rules$id.text) || '') + text;
23
+ if (styleSheet) {
24
+ styleSheet.insertRule(text, Object.keys(rules).length - 1);
25
+ }
13
26
  }
14
27
  };
15
28
  export const flush = () => {
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "@floating-ui/react-native": "^0.8.1",
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@telus-uds/system-constants": "^1.3.0",
14
- "@telus-uds/system-theme-tokens": "^2.46.0",
14
+ "@telus-uds/system-theme-tokens": "^2.48.0",
15
15
  "airbnb-prop-types": "^2.16.0",
16
16
  "css-mediaquery": "^0.1.2",
17
17
  "lodash.debounce": "^4.0.8",
@@ -74,5 +74,5 @@
74
74
  "standard-engine": {
75
75
  "skip": true
76
76
  },
77
- "version": "1.70.0"
77
+ "version": "1.72.0"
78
78
  }
@@ -241,31 +241,24 @@ const Autocomplete = forwardRef(
241
241
  }, [nestedSelectedValue, items])
242
242
 
243
243
  const handleClose = (event) => {
244
- if (event.type === 'keydown') {
245
- if (event.key === 'Escape' || event.key === 27) {
246
- setIsExpanded(false)
247
- setNestedSelectedValue(null)
248
- } else if (event.key === 'ArrowDown' && isExpanded && !isLoading && targetRef?.current) {
249
- targetRef.current.focus()
250
- }
251
- } else if (
252
- event.type === 'click' &&
253
- openOverlayRef?.current &&
254
- event.target &&
255
- !openOverlayRef?.current?.contains(event.target)
244
+ if (
245
+ (event.type === 'keydown' && (event.key === 'Escape' || event.key === '27')) ||
246
+ (event.type === 'click' && !openOverlayRef?.current?.contains(event.target)) ||
247
+ (event.type === 'touchstart' &&
248
+ openOverlayRef?.current &&
249
+ event.touches[0].target &&
250
+ !openOverlayRef?.current?.contains(event.touches[0].target))
256
251
  ) {
257
252
  setIsExpanded(false)
253
+ setNestedSelectedValue(null)
258
254
  } else if (
259
- event.type === 'touchstart' &&
260
- openOverlayRef?.current &&
261
- event.touches[0].target &&
262
- !openOverlayRef?.current?.contains(event.touches[0].target)
255
+ event.type === 'keydown' &&
256
+ event.key === 'ArrowDown' &&
257
+ isExpanded &&
258
+ !isLoading &&
259
+ targetRef?.current
263
260
  ) {
264
- setIsExpanded(false)
265
- } else if (Platform.OS === 'web') {
266
- // needed for dropdown to be collapsed when clicking outside on web
267
- setIsExpanded(false)
268
- setNestedSelectedValue(null)
261
+ targetRef.current.focus()
269
262
  }
270
263
  }
271
264
  const itemsToShow = currentValue
package/src/Card/Card.jsx CHANGED
@@ -1,11 +1,13 @@
1
1
  import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
+ import { View } from 'react-native'
3
4
 
4
- import { useThemeTokens } from '../ThemeProvider'
5
+ import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider'
5
6
  import { getTokensPropType, variantProp } from '../utils'
6
7
  import { useViewport } from '../ViewportProvider'
7
8
  import { a11yProps, selectSystemProps, viewProps } from '../utils/props'
8
9
  import CardBase from './CardBase'
10
+ import PressableCardBase from './PressableCardBase'
9
11
 
10
12
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
11
13
 
@@ -57,24 +59,84 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
57
59
  * you automatically make inaccessible its children, which may or may not be appropriate
58
60
  * depending on what you are trying to achieve.
59
61
  */
60
- const Card = forwardRef(({ children, tokens, variant, dataSet, ...rest }, ref) => {
61
- const viewport = useViewport()
62
- const themeTokens = useThemeTokens('Card', tokens, variant, { viewport })
62
+ const Card = forwardRef(
63
+ ({ children, tokens, variant, dataSet, onPress, interactiveCard, ...rest }, ref) => {
64
+ const viewport = useViewport()
65
+ const themeTokens = useThemeTokens('Card', tokens, variant, { viewport })
66
+ const getThemeTokens = useThemeTokensCallback('Card', interactiveCard?.tokens, {
67
+ interactive: true,
68
+ ...(interactiveCard?.variant || {})
69
+ })
70
+ // When interactive area is present, spacing tokens should only be applied
71
+ // to individual Card sections, not Card as a whole
72
+ const { paddingTop, paddingBottom, paddingLeft, paddingRight, ...restOfTokens } = themeTokens
63
73
 
64
- return (
65
- <CardBase ref={ref} tokens={themeTokens} dataSet={dataSet} {...selectProps(rest)}>
66
- {children}
67
- </CardBase>
68
- )
69
- })
74
+ return (
75
+ <>
76
+ <CardBase
77
+ ref={ref}
78
+ tokens={interactiveCard?.body ? restOfTokens : themeTokens}
79
+ dataSet={dataSet}
80
+ {...selectProps(rest)}
81
+ >
82
+ {interactiveCard?.body ? (
83
+ <>
84
+ <PressableCardBase
85
+ ref={ref}
86
+ tokens={getThemeTokens}
87
+ dataSet={dataSet}
88
+ onPress={onPress}
89
+ {...selectProps(rest)}
90
+ >
91
+ {interactiveCard?.body}
92
+ </PressableCardBase>
93
+ {children ? (
94
+ <View style={{ paddingTop, paddingBottom, paddingLeft, paddingRight }}>
95
+ {children}
96
+ </View>
97
+ ) : null}
98
+ </>
99
+ ) : (
100
+ children
101
+ )}
102
+ </CardBase>
103
+ </>
104
+ )
105
+ }
106
+ )
70
107
 
71
108
  Card.displayName = 'Card'
72
109
 
73
110
  Card.propTypes = {
74
111
  ...selectedSystemPropTypes,
112
+ /**
113
+ * Card content.
114
+ */
75
115
  children: PropTypes.node,
116
+ /**
117
+ * Card tokens.
118
+ */
76
119
  tokens: getTokensPropType('Card'),
77
- variant: variantProp.propType
120
+ /**
121
+ * Card variant.
122
+ */
123
+ variant: variantProp.propType,
124
+ /**
125
+ * Function to call on pressing the card.
126
+ * Note: This is only available when `interactiveCard` prop is used.
127
+ */
128
+ onPress: PropTypes.func,
129
+ /**
130
+ * Object to set interactive card's properties
131
+ * - body: The body of the interactive card, can be any renderable node
132
+ * - tokens: The tokens to be used for the interactive card
133
+ * - variant: The variant to be used for the interactive card
134
+ */
135
+ interactiveCard: PropTypes.shape({
136
+ body: PropTypes.node,
137
+ tokens: getTokensPropType('Card'),
138
+ variant: variantProp.propType
139
+ })
78
140
  }
79
141
 
80
142
  export default Card
@@ -54,6 +54,7 @@ const PressableCardBase = forwardRef(
54
54
  inactive,
55
55
  href,
56
56
  hrefAttrs,
57
+ dataSet,
57
58
  accessibilityRole = href ? 'link' : undefined,
58
59
  ...rawRest
59
60
  },
@@ -103,6 +104,7 @@ const PressableCardBase = forwardRef(
103
104
  onKeyDown={handleKeyDown}
104
105
  hrefAttrs={hrefAttrs}
105
106
  style={getOuterBorderStyle}
107
+ dataSet={dataSet}
106
108
  {...selectProps({ ...rest, accessibilityRole })}
107
109
  >
108
110
  {(pressableState) => (
@@ -0,0 +1,111 @@
1
+ import React, { forwardRef, useMemo } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { View, Pressable, Platform } from 'react-native'
5
+ import { resolvePressableTokens } from '../utils/pressability'
6
+ import { applyShadowToken } from '../ThemeProvider'
7
+
8
+ const selectGeneralBubbleTokens = ({
9
+ outerBubbleHeight,
10
+ outerBubbleWidth,
11
+ outerBubbleContentAlignItems,
12
+ outerBubbleJustifyContent,
13
+ bubbleBorderColor,
14
+ bubbleBorderWidth,
15
+ bubbleBorderRadius
16
+ }) => ({
17
+ height: outerBubbleHeight,
18
+ width: outerBubbleWidth,
19
+ justifyContent: outerBubbleContentAlignItems,
20
+ alignItems: outerBubbleJustifyContent,
21
+ borderColor: bubbleBorderColor,
22
+ borderWidth: bubbleBorderWidth,
23
+ borderRadius: bubbleBorderRadius,
24
+ ...Platform.select({ web: { outline: 'none' } })
25
+ })
26
+
27
+ const selectInnerBubbleTokens = ({
28
+ innerBubbleHeight,
29
+ innerBubbleWidth,
30
+ innerBubbleBorderRadius,
31
+ borderColor,
32
+ borderWidth,
33
+ shadow
34
+ }) => ({
35
+ height: innerBubbleHeight,
36
+ width: innerBubbleWidth,
37
+ borderRadius: innerBubbleBorderRadius,
38
+ borderColor,
39
+ borderWidth,
40
+ ...applyShadowToken(shadow)
41
+ })
42
+
43
+ const selectBorderBubbleTokens = ({
44
+ bubbleBorderColor,
45
+ bubbleBorderWidth,
46
+ bubbleBorderRadius
47
+ }) => ({
48
+ borderColor: bubbleBorderColor,
49
+ borderWidth: bubbleBorderWidth,
50
+ borderRadius: bubbleBorderRadius
51
+ })
52
+
53
+ const ColourBubble = forwardRef(
54
+ ({ tokens = {}, id, colourHexCode, colourName, isSelected, onPress }, ref) => {
55
+ const defaultTokens = tokens({ selected: isSelected })
56
+
57
+ const resolveColourBubbleTokens = (pressState) => resolvePressableTokens(tokens, pressState, {})
58
+
59
+ const themeTokens = useMemo(() => tokens(), [tokens])
60
+
61
+ return (
62
+ <Pressable
63
+ style={(state) => [
64
+ selectGeneralBubbleTokens(resolveColourBubbleTokens(state)),
65
+ isSelected && selectBorderBubbleTokens(defaultTokens)
66
+ ]}
67
+ onPress={onPress}
68
+ accessible
69
+ accessibilityRole="radio"
70
+ accessibilityLabel={colourName}
71
+ accessibilityState={{ checked: isSelected }}
72
+ ref={ref}
73
+ testID={id}
74
+ >
75
+ <View style={[selectInnerBubbleTokens(themeTokens), { backgroundColor: colourHexCode }]} />
76
+ </Pressable>
77
+ )
78
+ }
79
+ )
80
+ ColourBubble.displayName = 'ColourBubble'
81
+
82
+ ColourBubble.propTypes = {
83
+ /**
84
+ * Colour toggle tokens callback.
85
+ */
86
+ tokens: PropTypes.func,
87
+ /**
88
+ * ID of each colour bubble
89
+ */
90
+ id: PropTypes.string,
91
+ /**
92
+ * Hexadecimal code for the background of the colour bubble
93
+ */
94
+ colourHexCode: PropTypes.string,
95
+ /**
96
+ * Name of the colour bubble
97
+ */
98
+ colourName: PropTypes.string,
99
+ /**
100
+ * If the current colour bubble is selected
101
+ */
102
+ isSelected: PropTypes.bool,
103
+ /**
104
+ * If provided, this function is called when the current selection
105
+ * of the color is changed of all currently `items`.
106
+ * Receives two parameters: item object selected and the event
107
+ */
108
+ onPress: PropTypes.func
109
+ }
110
+
111
+ export default ColourBubble
@@ -0,0 +1,83 @@
1
+ import React, { forwardRef, useState } from 'react'
2
+ import { View } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+
5
+ import { useThemeTokensCallback } from '../ThemeProvider'
6
+ import { a11yProps, getTokensPropType, selectSystemProps, variantProp, viewProps } from '../utils'
7
+ import { StackWrap } from '../StackView'
8
+ import Typography from '../Typography'
9
+ import ColourBubble from './ColourBubble'
10
+
11
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
12
+
13
+ const ColourToggle = forwardRef(
14
+ ({ tokens, variant, defaultColourId, items, onChange, ...rest }, ref) => {
15
+ const [currentColourId, setCurrentColourId] = useState(defaultColourId)
16
+ const getTokens = useThemeTokensCallback('ColourToggle', tokens, variant)
17
+
18
+ const { space } = getTokens()
19
+ const { colourName: currentColourName = '' } =
20
+ items.find((item) => item.id === currentColourId) || ''
21
+
22
+ return (
23
+ <View ref={ref} {...selectProps(rest)}>
24
+ <Typography>{currentColourName}</Typography>
25
+ <StackWrap space={space} accessibilityRole="radiogroup">
26
+ {items.map(({ id, colourHexCode, colourName }, index) => {
27
+ const colourBubbleId = id || `ColourBubble[${index}]`
28
+
29
+ const handleChangeColour = (event) => {
30
+ setCurrentColourId(id)
31
+ onChange?.(event, { id, colourHexCode, colourName })
32
+ }
33
+
34
+ return (
35
+ <ColourBubble
36
+ key={colourBubbleId}
37
+ id={colourBubbleId}
38
+ tokens={getTokens}
39
+ isSelected={id === currentColourId}
40
+ colourHexCode={colourHexCode}
41
+ colourName={colourName}
42
+ onPress={handleChangeColour}
43
+ />
44
+ )
45
+ })}
46
+ </StackWrap>
47
+ </View>
48
+ )
49
+ }
50
+ )
51
+ ColourToggle.displayName = 'ColourToggle'
52
+
53
+ ColourToggle.propTypes = {
54
+ ...selectedSystemPropTypes,
55
+ /**
56
+ * Optional theme token overrides for the outer ColourToggle component
57
+ */
58
+ tokens: getTokensPropType('ColourToggle'),
59
+ /**
60
+ * Colour toggle variant.
61
+ */
62
+ variant: variantProp.propType,
63
+ /**
64
+ * Id of the selected color when component mounts
65
+ */
66
+ defaultColourId: PropTypes.string,
67
+ /**
68
+ * Array of objects containing specifics for each ColourBubble to be rendered in the group.
69
+ */
70
+ items: PropTypes.arrayOf(
71
+ PropTypes.exact({
72
+ colourHexCode: PropTypes.string,
73
+ colourName: PropTypes.string,
74
+ id: PropTypes.string
75
+ })
76
+ ),
77
+ /**
78
+ * If provided, this function is called when the current selection of the color is changed of all currently `items`. Receives two parameters: item object selected and the event
79
+ */
80
+ onChange: PropTypes.func
81
+ }
82
+
83
+ export default ColourToggle
@@ -0,0 +1,3 @@
1
+ import ColourToggle from './ColourToggle'
2
+
3
+ export default ColourToggle