@telus-uds/components-base 1.14.3 → 1.15.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 (139) hide show
  1. package/CHANGELOG.md +15 -2
  2. package/__tests17__/A11yText/A11yText.test.jsx +34 -0
  3. package/__tests17__/ActivityIndicator/ActivityIndicator.test.jsx +68 -0
  4. package/__tests17__/ActivityIndicator/__snapshots__/ActivityIndicator.test.jsx.snap +299 -0
  5. package/__tests17__/Box/Box.test.jsx +111 -0
  6. package/__tests17__/Button/Button.test.jsx +86 -0
  7. package/__tests17__/Button/ButtonBase.test.jsx +82 -0
  8. package/__tests17__/Button/ButtonGroup.test.jsx +347 -0
  9. package/__tests17__/Button/ButtonLink.test.jsx +61 -0
  10. package/__tests17__/Card/Card.test.jsx +63 -0
  11. package/__tests17__/Carousel/Carousel.test.jsx +128 -0
  12. package/__tests17__/Carousel/CarouselTabs.test.jsx +142 -0
  13. package/__tests17__/Checkbox/Checkbox.test.jsx +94 -0
  14. package/__tests17__/Checkbox/CheckboxGroup.test.jsx +246 -0
  15. package/__tests17__/Divider/Divider.test.jsx +91 -0
  16. package/__tests17__/ExpandCollapse/ExpandCollapse.test.jsx +109 -0
  17. package/__tests17__/Feedback/Feedback.test.jsx +42 -0
  18. package/__tests17__/FlexGrid/Col.test.jsx +261 -0
  19. package/__tests17__/FlexGrid/FlexGrid.test.jsx +136 -0
  20. package/__tests17__/FlexGrid/Row.test.jsx +273 -0
  21. package/__tests17__/HorizontalScroll/HorizontalScroll.test.jsx +165 -0
  22. package/__tests17__/Icon/Icon.test.jsx +61 -0
  23. package/__tests17__/IconButton/IconButton.test.jsx +52 -0
  24. package/__tests17__/InputLabel/InputLabel.test.jsx +28 -0
  25. package/__tests17__/InputLabel/__snapshots__/InputLabel.test.jsx.snap +3 -0
  26. package/__tests17__/InputSupports/InputSupports.test.jsx +60 -0
  27. package/__tests17__/Link/Link.test.jsx +63 -0
  28. package/__tests17__/Link/TextButton.test.jsx +35 -0
  29. package/__tests17__/List/List.test.jsx +82 -0
  30. package/__tests17__/Modal/Modal.test.jsx +47 -0
  31. package/__tests17__/Notification/Notification.test.jsx +20 -0
  32. package/__tests17__/Pagination/Pagination.test.jsx +160 -0
  33. package/__tests17__/Progress/Progress.test.jsx +79 -0
  34. package/__tests17__/Radio/Radio.test.jsx +87 -0
  35. package/__tests17__/Radio/RadioGroup.test.jsx +220 -0
  36. package/__tests17__/RadioCard/RadioCard.test.jsx +87 -0
  37. package/__tests17__/RadioCard/RadioCardGroup.test.jsx +246 -0
  38. package/__tests17__/Search/Search.test.jsx +87 -0
  39. package/__tests17__/Select/Select.test.jsx +94 -0
  40. package/__tests17__/SideNav/SideNav.test.jsx +110 -0
  41. package/__tests17__/Skeleton/Skeleton.test.jsx +61 -0
  42. package/__tests17__/SkipLink/SkipLink.test.jsx +61 -0
  43. package/__tests17__/Spacer/Spacer.test.jsx +63 -0
  44. package/__tests17__/StackView/StackView.test.jsx +211 -0
  45. package/__tests17__/StackView/StackWrap.test.jsx +47 -0
  46. package/__tests17__/StackView/getStackedContent.test.jsx +295 -0
  47. package/__tests17__/StepTracker/StepTracker.test.jsx +108 -0
  48. package/__tests17__/Tabs/Tabs.test.jsx +49 -0
  49. package/__tests17__/Tags/Tags.test.jsx +327 -0
  50. package/__tests17__/TextInput/TextArea.test.jsx +35 -0
  51. package/__tests17__/TextInput/TextInputBase.test.jsx +125 -0
  52. package/__tests17__/ThemeProvider/ThemeProvider.test.jsx +80 -0
  53. package/__tests17__/ThemeProvider/useThemeTokens.test.jsx +514 -0
  54. package/__tests17__/ThemeProvider/utils/theme-tokens.test.js +41 -0
  55. package/__tests17__/ToggleSwitch/ToggleSwitch.test.jsx +82 -0
  56. package/__tests17__/ToggleSwitch/ToggleSwitchGroup.test.jsx +192 -0
  57. package/__tests17__/Tooltip/Tooltip.test.jsx +65 -0
  58. package/__tests17__/Tooltip/getTooltipPosition.test.js +79 -0
  59. package/__tests17__/Typography/typography.test.jsx +90 -0
  60. package/__tests17__/utils/children.test.jsx +128 -0
  61. package/__tests17__/utils/containUniqueFields.test.js +25 -0
  62. package/__tests17__/utils/input.test.js +375 -0
  63. package/__tests17__/utils/props.test.js +36 -0
  64. package/__tests17__/utils/semantics.test.jsx +34 -0
  65. package/__tests17__/utils/useCopy.test.js +42 -0
  66. package/__tests17__/utils/useResponsiveProp.test.jsx +202 -0
  67. package/__tests17__/utils/useSpacingScale.test.jsx +273 -0
  68. package/__tests17__/utils/useUniqueId.test.js +31 -0
  69. package/component-docs.json +85 -438
  70. package/lib/A11yInfoProvider/index.js +14 -5
  71. package/lib/Button/ButtonGroup.js +3 -2
  72. package/lib/Checkbox/Checkbox.js +9 -6
  73. package/lib/ExpandCollapse/Control.js +6 -5
  74. package/lib/ExpandCollapse/Panel.js +5 -4
  75. package/lib/List/ListItem.js +10 -236
  76. package/lib/List/ListItemBase.js +162 -0
  77. package/lib/List/ListItemContent.js +85 -0
  78. package/lib/List/ListItemMark.js +158 -0
  79. package/lib/List/PressableListItemBase.js +147 -0
  80. package/lib/Notification/Notification.js +2 -1
  81. package/lib/Pagination/Pagination.js +4 -3
  82. package/lib/Radio/Radio.js +9 -6
  83. package/lib/RadioCard/RadioCard.js +9 -6
  84. package/lib/Tabs/Tabs.js +12 -3
  85. package/lib/Tags/Tags.js +3 -3
  86. package/lib/TextInput/TextInput.js +5 -4
  87. package/lib/ViewportProvider/useViewportListener.js +11 -5
  88. package/lib/utils/hasOwnProperty.js +18 -0
  89. package/lib/utils/props/a11yProps.js +212 -45
  90. package/lib/utils/props/getPropSelector.js +47 -5
  91. package/lib/utils/useResponsiveProp.js +5 -3
  92. package/lib/utils/withLinkRouter.js +3 -5
  93. package/lib-module/A11yInfoProvider/index.js +14 -4
  94. package/lib-module/Button/ButtonGroup.js +3 -2
  95. package/lib-module/Checkbox/Checkbox.js +9 -6
  96. package/lib-module/ExpandCollapse/Control.js +6 -5
  97. package/lib-module/ExpandCollapse/Panel.js +5 -4
  98. package/lib-module/List/ListItem.js +13 -235
  99. package/lib-module/List/ListItemBase.js +139 -0
  100. package/lib-module/List/ListItemContent.js +66 -0
  101. package/lib-module/List/ListItemMark.js +143 -0
  102. package/lib-module/List/PressableListItemBase.js +117 -0
  103. package/lib-module/Notification/Notification.js +2 -1
  104. package/lib-module/Pagination/Pagination.js +5 -3
  105. package/lib-module/Radio/Radio.js +9 -6
  106. package/lib-module/RadioCard/RadioCard.js +9 -6
  107. package/lib-module/Tabs/Tabs.js +13 -4
  108. package/lib-module/Tags/Tags.js +3 -3
  109. package/lib-module/TextInput/TextInput.js +5 -4
  110. package/lib-module/ViewportProvider/useViewportListener.js +10 -4
  111. package/lib-module/utils/hasOwnProperty.js +11 -0
  112. package/lib-module/utils/props/a11yProps.js +210 -45
  113. package/lib-module/utils/props/getPropSelector.js +44 -5
  114. package/lib-module/utils/useResponsiveProp.js +3 -4
  115. package/lib-module/utils/withLinkRouter.js +3 -5
  116. package/package.json +11 -16
  117. package/src/A11yInfoProvider/index.jsx +20 -4
  118. package/src/Button/ButtonGroup.jsx +4 -2
  119. package/src/Checkbox/Checkbox.jsx +7 -3
  120. package/src/ExpandCollapse/Control.jsx +8 -5
  121. package/src/ExpandCollapse/Panel.jsx +7 -5
  122. package/src/List/ListItem.jsx +12 -191
  123. package/src/List/ListItemBase.jsx +118 -0
  124. package/src/List/ListItemContent.jsx +52 -0
  125. package/src/List/ListItemMark.jsx +99 -0
  126. package/src/List/PressableListItemBase.jsx +102 -0
  127. package/src/Notification/Notification.jsx +1 -1
  128. package/src/Pagination/Pagination.jsx +6 -1
  129. package/src/Radio/Radio.jsx +7 -3
  130. package/src/RadioCard/RadioCard.jsx +7 -3
  131. package/src/Tabs/Tabs.jsx +19 -2
  132. package/src/Tags/Tags.jsx +3 -3
  133. package/src/TextInput/TextInput.jsx +4 -4
  134. package/src/ViewportProvider/useViewportListener.js +10 -5
  135. package/src/utils/hasOwnProperty.js +11 -0
  136. package/src/utils/props/a11yProps.js +168 -55
  137. package/src/utils/props/getPropSelector.js +45 -4
  138. package/src/utils/useResponsiveProp.js +3 -3
  139. package/src/utils/withLinkRouter.jsx +1 -3
@@ -1,9 +1,48 @@
1
- export default function getPropSelector(propTypes, regexp) {
2
- const keys = Object.keys(propTypes);
1
+ import merge from 'lodash.merge';
2
+ import hasOwnProperty from '../hasOwnProperty';
3
+ /**
4
+ * @callback PropSelectorCallback - a callback called for each prop passed to a component
5
+ * @param {string} key - the key for the prop to be tested
6
+ * @param {*} value - the value of the prop being passed in to the component
7
+ * @returns {object|undefined}
8
+ */
9
+
10
+ /**
11
+ * @param {PropSelectorCallback} callback
12
+ * @param {object} items
13
+ * @param {string} key
14
+ * @param {*} value
15
+ * @returns {object|undefined}
16
+ */
17
+
18
+ const applyCallback = (callback, items, key, value) => {
19
+ // If there's no callback, continue and look up keys as normal
20
+ if (typeof callback !== 'function') return undefined;
21
+ const newItems = callback(key, value); // If the callback doesn't return anything, continue and look up keys as normal
22
+
23
+ if (!newItems) return undefined; // If the callback returns items, merge them in, deep merging props that are objects
24
+
25
+ return merge({}, items, newItems);
26
+ };
27
+ /**
28
+ * Generates a function to filter an object of props down to a subset of allowed props, with
29
+ * optional prop alteration and re-mapping via an optional callback.
30
+ *
31
+ * @param {object} propTypes - an object where every defined key is a valid prop
32
+ * @param {*} [regexp] - an optional regular expression where any match is a valid prop
33
+ * @param {PropSelectorCallback} callback - optional function taking `(key, value)` returning either undefined or an object of new props to merge in
34
+ * @returns {object} - valid props for this component
35
+ */
36
+
37
+
38
+ export default function getPropSelector(propTypes, regexp, callback) {
3
39
  return props => Object.entries(props).reduce((items, _ref) => {
4
40
  let [key, value] = _ref;
5
- return keys.includes(key) || regexp && regexp.test(key) ? { ...items,
6
- [key]: value
7
- } : items;
41
+ return (// If there's a callback and it matches something, applyCallback merges it in; return that
42
+ applyCallback(callback, items, key, value) || ( // If there's no callback match, check if this prop is valid and merge it in if it is
43
+ hasOwnProperty(propTypes, key) || regexp && regexp.test(key) ? { ...items,
44
+ [key]: value
45
+ } : items)
46
+ );
8
47
  }, {});
9
48
  }
@@ -1,9 +1,8 @@
1
1
  import { viewports } from '@telus-uds/system-constants';
2
2
  import { useViewport } from '../ViewportProvider';
3
+ import hasOwnProperty from './hasOwnProperty';
3
4
 
4
- const hasOwn = (objectProp, key) => Object.prototype.hasOwnProperty.call(objectProp, key);
5
-
6
- const hasResponsiveProperties = objectProp => viewports.keys.some(key => hasOwn(objectProp, key));
5
+ const hasResponsiveProperties = objectProp => viewports.keys.some(key => hasOwnProperty(objectProp, key));
7
6
  /**
8
7
  * Resolves a prop which may be a responsive object with keys for viewports.
9
8
  *
@@ -21,7 +20,7 @@ export const resolveResponsiveProp = (prop, viewport, defaultValue) => {
21
20
  if (!prop || typeof prop !== 'object' || !hasResponsiveProperties(prop)) return prop;
22
21
  const value = viewports.keys.includes(viewport) ? // If there's a current viewport, return the closest match at or below it
23
22
  viewports.inherit(prop)[viewport] : // If no current viewport is available, default to smallest viewport
24
- prop[viewports.keys.find(key => hasOwn(prop, key))];
23
+ prop[viewports.keys.find(key => hasOwnProperty(prop, key))];
25
24
  return value === undefined ? defaultValue : value;
26
25
  };
27
26
  /**
@@ -1,9 +1,6 @@
1
1
  import React, { forwardRef } from 'react';
2
- import PropTypes from 'prop-types'; // Prototype-safe alternative to (linter-forbidden) someObject.hasOwnProperty()
3
-
4
- import { jsx as _jsx } from "react/jsx-runtime";
5
-
6
- const hasOwnProperty = (object, prop) => Object.prototype.hasOwnProperty.call(object, prop);
2
+ import PropTypes from 'prop-types';
3
+ import hasOwnProperty from './hasOwnProperty';
7
4
  /**
8
5
  * Higher-order component that has no effect unless an additional prop `LinkRouter` is passed.
9
6
  * This may be used to provide custom wrappers for integrations with third party libraries.
@@ -33,6 +30,7 @@ const hasOwnProperty = (object, prop) => Object.prototype.hasOwnProperty.call(ob
33
30
  * ```
34
31
  */
35
32
 
33
+ import { jsx as _jsx } from "react/jsx-runtime";
36
34
 
37
35
  const withLinkRouter = Component => {
38
36
  const wrappedComponent = /*#__PURE__*/forwardRef((_ref, ref) => {
package/package.json CHANGED
@@ -18,17 +18,14 @@
18
18
  },
19
19
  "description": "Base components",
20
20
  "devDependencies": {
21
- "@storybook/addon-a11y": "^6.5.6",
22
- "@storybook/addon-essentials": "^6.5.6",
23
- "@storybook/cli": "^6.5.6",
24
- "@storybook/react": "^6.5.6",
25
- "@storybook/builder-webpack5": "^6.5.6",
26
- "@storybook/manager-webpack5": "^6.5.6",
27
21
  "@telus-uds/browserslist-config": "^1.0.4",
28
22
  "@testing-library/jest-native": "^4.0.1",
29
- "@testing-library/react-hooks": "^7.0.1",
30
- "@testing-library/react-native": "^7.2.0",
31
- "react-test-renderer": "^16.3.2",
23
+ "@testing-library/react": "^13.3.0",
24
+ "@testing-library/react-native": "11.0.0",
25
+ "react-test-renderer": "~18.0.0",
26
+ "@testing-library/react-hooks": "~7.0.1",
27
+ "@testing-library/react-native-17": "npm:@testing-library/react-native@7.2.0",
28
+ "react-test-renderer-17": "npm:react-test-renderer@17.0.2",
32
29
  "webpack": "5.x"
33
30
  },
34
31
  "directories": {
@@ -44,10 +41,10 @@
44
41
  "module": "lib-module/index.js",
45
42
  "name": "@telus-uds/components-base",
46
43
  "peerDependencies": {
47
- "react": "^17.0.2",
48
- "react-dom": "^17.0.2",
44
+ "react": "^17.0.2 || ~18.0.0",
45
+ "react-dom": "^17.0.2 || ~18.0.0",
49
46
  "react-native": "*",
50
- "react-native-web": "~0.17.5"
47
+ "react-native-web": "~0.17.7 || ~0.18.7"
51
48
  },
52
49
  "react-native": "src/index.js",
53
50
  "repository": {
@@ -63,13 +60,11 @@
63
60
  "build:main": "babel src -d lib",
64
61
  "build:module": "babel src -d lib-module --env-name module",
65
62
  "build:code": "npm run build:main && npm run build:module",
66
- "build:docs": "babel-node --plugins=@nearform/babel-plugin-react-docgen generate-component-docs.js",
67
- "storybook": "start-storybook",
68
- "build-storybook": "build-storybook"
63
+ "build:docs": "babel-node --plugins=@nearform/babel-plugin-react-docgen generate-component-docs.js"
69
64
  },
70
65
  "sideEffects": false,
71
66
  "standard-engine": {
72
67
  "skip": true
73
68
  },
74
- "version": "1.14.3"
69
+ "version": "1.15.0"
75
70
  }
@@ -16,8 +16,14 @@ const A11yInfoProvider = ({ children }) => {
16
16
  return () => {}
17
17
  }
18
18
 
19
- AccessibilityInfo.addEventListener('reduceMotionChanged', setReduceMotionEnabled)
20
- AccessibilityInfo.addEventListener('screenReaderChanged', setScreenReaderEnabled)
19
+ const motionSubscription = AccessibilityInfo.addEventListener(
20
+ 'reduceMotionChanged',
21
+ setReduceMotionEnabled
22
+ )
23
+ const screenReaderSubscription = AccessibilityInfo.addEventListener(
24
+ 'screenReaderChanged',
25
+ setScreenReaderEnabled
26
+ )
21
27
 
22
28
  const setInitialA11yInfo = async () => {
23
29
  const [initialReduceMotionEnabled, initialScreenReaderEnabled] = await Promise.all([
@@ -38,8 +44,18 @@ const A11yInfoProvider = ({ children }) => {
38
44
  }
39
45
 
40
46
  return () => {
41
- AccessibilityInfo.removeEventListener('reduceMotionChanged', setReduceMotionEnabled)
42
- AccessibilityInfo.removeEventListener('screenReaderChanged', setScreenReaderEnabled)
47
+ // From react-native 0.65, AccessibilityInfo.removeEventListener is deprecated for `remove` on addEventListener return value
48
+ if (typeof motionSubscription?.remove === 'function') {
49
+ motionSubscription?.remove()
50
+ } else if (typeof AccessibilityInfo.removeEventListener === 'function') {
51
+ AccessibilityInfo.removeEventListener('reduceMotionChanged', setReduceMotionEnabled)
52
+ }
53
+
54
+ if (typeof screenReaderSubscription?.remove === 'function') {
55
+ screenReaderSubscription?.remove()
56
+ } else if (typeof AccessibilityInfo.removeEventListener === 'function') {
57
+ AccessibilityInfo.removeEventListener('screenReaderChanged', setScreenReaderEnabled)
58
+ }
43
59
  }
44
60
  }, [])
45
61
 
@@ -141,8 +141,10 @@ const ButtonGroup = forwardRef(
141
141
  tokens={getButtonTokens}
142
142
  selected={isSelected}
143
143
  inactive={inactive}
144
- {...itemA11y}
145
- {...selectItemProps(itemRest)}
144
+ {...selectItemProps({
145
+ ...itemRest,
146
+ ...itemA11y
147
+ })}
146
148
  >
147
149
  {label}
148
150
  </ButtonBase>
@@ -176,6 +176,12 @@ const Checkbox = forwardRef(
176
176
  const inputId = id ?? uniqueId
177
177
  const { themeOptions } = useTheme()
178
178
 
179
+ const selectedProps = selectProps({
180
+ accessibilityRole: 'checkbox',
181
+ accessibilityState: { checked: isChecked, disabled: inactive },
182
+ ...rest
183
+ })
184
+
179
185
  return (
180
186
  <View style={staticStyles.wrapper} ref={ref}>
181
187
  <StackView direction={feedbackPosition === 'top' ? 'column-reverse' : 'column'} space={0}>
@@ -183,9 +189,7 @@ const Checkbox = forwardRef(
183
189
  disabled={inactive}
184
190
  onKeyDown={handleKeyDown}
185
191
  onPress={handleChange}
186
- accessibilityRole="checkbox"
187
- accessibilityState={{ checked: isChecked, disabled: inactive }}
188
- {...selectProps(rest)}
192
+ {...selectedProps}
189
193
  >
190
194
  {({ focused: focus, hovered: hover, pressed }) => {
191
195
  const { icon: IconComponent, ...stateTokens } = getTokens({ focus, hover, pressed })
@@ -56,11 +56,14 @@ const ExpandCollapseControl = forwardRef(
56
56
  ) => {
57
57
  const getTokens = useThemeTokensCallback('ExpandCollapseControl', tokens, variant)
58
58
 
59
- const selectedProps = selectProps({ ...rest, accessibilityRole })
60
- selectedProps.accessibilityState = {
61
- ...(selectedProps.accessibilityState || {}),
62
- expanded: isExpanded
63
- }
59
+ const selectedProps = selectProps({
60
+ accessibilityRole,
61
+ ...rest,
62
+ accessibilityState: {
63
+ ...(rest.accessibilityState || {}),
64
+ expanded: isExpanded
65
+ }
66
+ })
64
67
 
65
68
  const getControlState = ({ pressed, hovered, focused }) => ({
66
69
  pressed,
@@ -60,11 +60,13 @@ const ExpandCollapsePanel = forwardRef(
60
60
  const [containerHeight, setContainerHeight] = useState(null)
61
61
  const isExpanded = openIds.includes(panelId)
62
62
 
63
- const selectedProps = selectProps(rest)
64
- selectedProps.accessibilityState = {
65
- ...(selectedProps.accessibilityState || {}),
66
- expanded: isExpanded
67
- }
63
+ const selectedProps = selectProps({
64
+ ...rest,
65
+ accessibilityState: {
66
+ ...(rest.accessibilityState || {}),
67
+ expanded: isExpanded
68
+ }
69
+ })
68
70
 
69
71
  const themeTokens = useThemeTokens('ExpandCollapsePanel', tokens, variant, {
70
72
  expanded: isExpanded
@@ -1,204 +1,25 @@
1
1
  import React, { forwardRef } from 'react'
2
- import { View, Platform, StyleSheet } from 'react-native'
3
- import PropTypes from 'prop-types'
4
- import { useTheme, useThemeTokens, applyTextStyles } from '../ThemeProvider'
5
- import {
6
- a11yProps,
7
- getTokensPropType,
8
- selectSystemProps,
9
- variantProp,
10
- viewProps,
11
- wrapStringsInText
12
- } from '../utils'
13
2
 
14
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
15
-
16
- const selectBulletStyles = ({ itemBulletWidth, itemBulletHeight, itemBulletColor }) => ({
17
- width: itemBulletWidth,
18
- height: itemBulletHeight,
19
- borderRadius: itemBulletHeight / 2,
20
- backgroundColor: itemBulletColor
21
- })
22
-
23
- const selectBulletContainerStyles = ({ itemBulletContainerWidth, itemBulletContainerAlign }) => ({
24
- width: itemBulletContainerWidth,
25
- alignItems: itemBulletContainerAlign
26
- })
27
-
28
- const selectItemIconTokens = ({ itemIconSize, itemIconColor }) => ({
29
- size: itemIconSize,
30
- color: itemIconColor
31
- })
32
-
33
- const selectSideItemContainerStyles = ({ listGutter, iconMarginTop }) => ({
34
- marginTop: iconMarginTop,
35
- marginRight: listGutter
36
- })
37
-
38
- // Align bullets with the top line of text the same way icons are aligned
39
- const selectBulletPositioningStyles = ({ itemIconSize }) => ({
40
- width: itemIconSize,
41
- height: itemIconSize
42
- })
43
-
44
- const selectItemStyles = (
45
- { itemFontWeight, itemFontSize, itemLineHeight, itemFontName },
46
- themeOptions
47
- ) =>
48
- applyTextStyles({
49
- fontWeight: itemFontWeight,
50
- fontSize: itemFontSize,
51
- lineHeight: itemLineHeight,
52
- fontName: itemFontName,
53
- themeOptions
54
- })
55
-
56
- const selectItemBlockStyles = ({ interItemMargin }) => ({
57
- marginBottom: interItemMargin
58
- })
59
-
60
- const selectDividerStyles = ({ dividerColor, dividerSize, interItemMarginWithDivider }) => ({
61
- borderBottomWidth: dividerSize,
62
- borderColor: dividerColor,
63
- marginBottom: interItemMarginWithDivider,
64
- paddingBottom: interItemMarginWithDivider
65
- })
3
+ import ListItemBase from './ListItemBase'
4
+ import { useThemeTokens } from '../ThemeProvider'
5
+ import { variantProp } from '../utils'
66
6
 
67
7
  /**
68
8
  * ListItem is responsible for rendering icon or a bullet as side item
69
9
  */
70
- const ListItem = forwardRef(
71
- (
72
- {
73
- tokens,
74
- variant,
75
- icon,
76
- iconColor,
77
- iconSize,
78
- showDivider,
79
- children,
80
- isLastItem,
81
- accessibilityRole = Platform.select({ web: 'listitem', default: undefined }),
82
- ...rest
83
- },
84
- ref
85
- ) => {
86
- const themeTokens = useThemeTokens('List', tokens, variant)
87
- const { themeOptions } = useTheme()
88
-
89
- const itemStyles = selectItemStyles(themeTokens, themeOptions)
90
- const itemBlockStyles = selectItemBlockStyles(themeTokens)
91
- const dividerStyles = selectDividerStyles(themeTokens)
92
- const itemBulletContainerStyles = selectBulletContainerStyles(themeTokens)
93
- const itemBulletStyles = selectBulletStyles(themeTokens)
94
- const itemBulletPositioningStyles = selectBulletPositioningStyles(themeTokens)
95
- const iconTokens = selectItemIconTokens(themeTokens)
96
- const sideItemContainerStyles = selectSideItemContainerStyles(themeTokens)
97
-
98
- const renderItem = () => (
99
- <View style={staticStyles.wrap}>{wrapStringsInText(children, { style: itemStyles })}</View>
100
- )
101
-
102
- /**
103
- * Function responsible returning styling, in case the item is the last shouldn't
104
- * add extra margin on the bottom, if "showDivider" is true it should add a divider
105
- * and custom margin and padding, otherwise just adds a margin to the bottom
106
- */
107
- const getContainerStyle = () => {
108
- if (isLastItem) {
109
- return undefined
110
- }
111
-
112
- if (showDivider) {
113
- return dividerStyles
114
- }
115
-
116
- return itemBlockStyles
117
- }
118
-
119
- /**
120
- * Renders item bullet or Icon in case it's defined
121
- * in case children are string the icon is centered otherwise
122
- * it will align itself at start of the container
123
- */
124
- const renderMarker = () => {
125
- const IconComponent = icon || <></>
126
-
127
- if (icon) {
128
- return (
129
- <View style={sideItemContainerStyles}>
130
- <IconComponent
131
- size={iconSize || iconTokens.size}
132
- color={iconColor || iconTokens.color}
133
- />
134
- </View>
135
- )
136
- }
137
-
138
- return (
139
- <View style={[sideItemContainerStyles, itemBulletContainerStyles]}>
140
- <View style={[staticStyles.bulletPositioning, itemBulletPositioningStyles]}>
141
- <View style={itemBulletStyles} testID="unordered-item-bullet" />
142
- </View>
143
- </View>
144
- )
145
- }
146
-
147
- return (
148
- <View
149
- ref={ref}
150
- style={[staticStyles.itemContainer, getContainerStyle()]}
151
- accessibilityRole={accessibilityRole}
152
- {...selectProps(rest)}
153
- >
154
- {renderMarker()}
155
- {renderItem()}
156
- </View>
157
- )
158
- }
159
- )
160
- ListItem.displayName = 'ListItem'
161
-
162
- const staticStyles = StyleSheet.create({
163
- itemContainer: {
164
- flexDirection: 'row'
165
- },
166
- wrap: {
167
- flex: 1
168
- },
169
- bulletPositioning: {
170
- alignItems: 'center',
171
- justifyContent: 'center'
172
- }
10
+ const ListItem = forwardRef(({ tokens, variant, children, ...listItemProps }, ref) => {
11
+ const themeTokens = useThemeTokens('List', tokens, variant)
12
+ return (
13
+ <ListItemBase tokens={themeTokens} ref={ref} {...listItemProps}>
14
+ {children}
15
+ </ListItemBase>
16
+ )
173
17
  })
18
+ ListItem.displayName = 'ListItem'
174
19
 
175
20
  ListItem.propTypes = {
176
- ...selectedSystemPropTypes,
177
- tokens: getTokensPropType('List'),
178
21
  variant: variantProp.propType,
179
- children: PropTypes.node.isRequired,
180
- /**
181
- * Renders side item icon
182
- */
183
- icon: PropTypes.elementType,
184
- /**
185
- * Will set display icon color
186
- */
187
- iconColor: PropTypes.string,
188
- /**
189
- * Allow the user define the icon size if not defined the theme's file
190
- */
191
- iconSize: PropTypes.number,
192
- /**
193
- * @ignore
194
- * Defined by parent if it's last item on the list
195
- */
196
- isLastItem: PropTypes.bool,
197
- /**
198
- * @ignore
199
- * In case it is not the last item allow display divider
200
- */
201
- showDivider: PropTypes.bool
22
+ ...ListItemBase.propTypes
202
23
  }
203
24
 
204
25
  export default ListItem
@@ -0,0 +1,118 @@
1
+ import React, { forwardRef } from 'react'
2
+ import { View, Platform, StyleSheet } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+ import { a11yProps, getTokensPropType, selectSystemProps, variantProp, viewProps } from '../utils'
5
+
6
+ import ListItemContent from './ListItemContent'
7
+ import ListItemMark from './ListItemMark'
8
+
9
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
10
+
11
+ const selectItemBlockStyles = ({ interItemMargin }) => ({
12
+ marginBottom: interItemMargin
13
+ })
14
+
15
+ const selectDividerStyles = ({ dividerColor, dividerSize, interItemMarginWithDivider }) => ({
16
+ borderBottomWidth: dividerSize,
17
+ borderColor: dividerColor,
18
+ marginBottom: interItemMarginWithDivider,
19
+ paddingBottom: interItemMarginWithDivider
20
+ })
21
+
22
+ /**
23
+ * ListItem is responsible for rendering icon or a bullet as side item
24
+ */
25
+ const ListItemBase = forwardRef(
26
+ (
27
+ {
28
+ tokens,
29
+ icon,
30
+ iconColor,
31
+ iconSize,
32
+ showDivider,
33
+ children,
34
+ isLastItem,
35
+ accessibilityRole = Platform.select({ web: 'listitem', default: undefined }),
36
+ ...rest
37
+ },
38
+ ref
39
+ ) => {
40
+ const themeTokens = typeof tokens === 'function' ? tokens() : tokens
41
+
42
+ const itemBlockStyles = selectItemBlockStyles(themeTokens)
43
+ const dividerStyles = selectDividerStyles(themeTokens)
44
+
45
+ /**
46
+ * Function responsible returning styling, in case the item is the last shouldn't
47
+ * add extra margin on the bottom, if "showDivider" is true it should add a divider
48
+ * and custom margin and padding, otherwise just adds a margin to the bottom
49
+ */
50
+ const getContainerStyle = () => {
51
+ if (isLastItem) {
52
+ return undefined
53
+ }
54
+
55
+ if (showDivider) {
56
+ return dividerStyles
57
+ }
58
+
59
+ return itemBlockStyles
60
+ }
61
+
62
+ return (
63
+ <View
64
+ ref={ref}
65
+ style={[staticStyles.itemContainer, getContainerStyle()]}
66
+ accessibilityRole={accessibilityRole}
67
+ {...selectProps(rest)}
68
+ >
69
+ {typeof children === 'function' ? (
70
+ children({ tokens, icon, iconColor, iconSize, isLastItem })
71
+ ) : (
72
+ <>
73
+ <ListItemMark tokens={tokens} icon={icon} iconColor={iconColor} iconSize={iconSize} />
74
+ <ListItemContent tokens={tokens}>{children}</ListItemContent>
75
+ </>
76
+ )}
77
+ </View>
78
+ )
79
+ }
80
+ )
81
+ ListItemBase.displayName = 'ListItem'
82
+
83
+ const staticStyles = StyleSheet.create({
84
+ itemContainer: {
85
+ flexDirection: 'row'
86
+ }
87
+ })
88
+
89
+ ListItemBase.propTypes = {
90
+ ...selectedSystemPropTypes,
91
+ tokens: getTokensPropType('List'),
92
+ variant: variantProp.propType,
93
+ children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
94
+ /**
95
+ * Renders side item icon
96
+ */
97
+ icon: PropTypes.elementType,
98
+ /**
99
+ * Will set display icon color
100
+ */
101
+ iconColor: PropTypes.string,
102
+ /**
103
+ * Allow the user define the icon size if not defined the theme's file
104
+ */
105
+ iconSize: PropTypes.number,
106
+ /**
107
+ * @ignore
108
+ * Defined by parent if it's last item on the list
109
+ */
110
+ isLastItem: PropTypes.bool,
111
+ /**
112
+ * @ignore
113
+ * In case it is not the last item allow display divider
114
+ */
115
+ showDivider: PropTypes.bool
116
+ }
117
+
118
+ export default ListItemBase
@@ -0,0 +1,52 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { View, StyleSheet } from 'react-native'
5
+ import { wrapStringsInText } from '../utils'
6
+ import { useTheme, applyTextStyles } from '../ThemeProvider'
7
+
8
+ export const tokenTypes = {
9
+ itemFontWeight: PropTypes.string.isRequired,
10
+ itemFontSize: PropTypes.number.isRequired,
11
+ itemLineHeight: PropTypes.number.isRequired,
12
+ itemFontName: PropTypes.string.isRequired
13
+ }
14
+
15
+ const selectItemTextStyles = (
16
+ { itemFontWeight, itemFontSize, itemLineHeight, itemFontName },
17
+ themeOptions
18
+ ) =>
19
+ applyTextStyles({
20
+ fontWeight: itemFontWeight,
21
+ fontSize: itemFontSize,
22
+ lineHeight: itemLineHeight,
23
+ fontName: itemFontName,
24
+ themeOptions
25
+ })
26
+
27
+ /**
28
+ * Subcomponent used within ListItem and similar components for rendering content that fills
29
+ * and wraps available space in a { flexDirection: row } container alongside a ListIconMark,
30
+ * and that applies text styles to strings via supplied tokens.
31
+ *
32
+ * It's the responsibility of themes to make sure that these text tokens align the first line of
33
+ * text nicely against the bullet or icon rendered by ListIconMark.
34
+ */
35
+ const ListItemContent = ({ tokens, children }) => {
36
+ const { themeOptions } = useTheme()
37
+ const textStyles = selectItemTextStyles(tokens, themeOptions)
38
+ return <View style={staticStyles.wrap}>{wrapStringsInText(children, { style: textStyles })}</View>
39
+ }
40
+
41
+ const staticStyles = StyleSheet.create({
42
+ wrap: {
43
+ flex: 1
44
+ }
45
+ })
46
+
47
+ ListItemContent.propTypes = {
48
+ tokens: PropTypes.shape(tokenTypes).isRequired,
49
+ children: PropTypes.node.isRequired
50
+ }
51
+
52
+ export default ListItemContent