@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.
- package/CHANGELOG.md +15 -2
- package/__tests17__/A11yText/A11yText.test.jsx +34 -0
- package/__tests17__/ActivityIndicator/ActivityIndicator.test.jsx +68 -0
- package/__tests17__/ActivityIndicator/__snapshots__/ActivityIndicator.test.jsx.snap +299 -0
- package/__tests17__/Box/Box.test.jsx +111 -0
- package/__tests17__/Button/Button.test.jsx +86 -0
- package/__tests17__/Button/ButtonBase.test.jsx +82 -0
- package/__tests17__/Button/ButtonGroup.test.jsx +347 -0
- package/__tests17__/Button/ButtonLink.test.jsx +61 -0
- package/__tests17__/Card/Card.test.jsx +63 -0
- package/__tests17__/Carousel/Carousel.test.jsx +128 -0
- package/__tests17__/Carousel/CarouselTabs.test.jsx +142 -0
- package/__tests17__/Checkbox/Checkbox.test.jsx +94 -0
- package/__tests17__/Checkbox/CheckboxGroup.test.jsx +246 -0
- package/__tests17__/Divider/Divider.test.jsx +91 -0
- package/__tests17__/ExpandCollapse/ExpandCollapse.test.jsx +109 -0
- package/__tests17__/Feedback/Feedback.test.jsx +42 -0
- package/__tests17__/FlexGrid/Col.test.jsx +261 -0
- package/__tests17__/FlexGrid/FlexGrid.test.jsx +136 -0
- package/__tests17__/FlexGrid/Row.test.jsx +273 -0
- package/__tests17__/HorizontalScroll/HorizontalScroll.test.jsx +165 -0
- package/__tests17__/Icon/Icon.test.jsx +61 -0
- package/__tests17__/IconButton/IconButton.test.jsx +52 -0
- package/__tests17__/InputLabel/InputLabel.test.jsx +28 -0
- package/__tests17__/InputLabel/__snapshots__/InputLabel.test.jsx.snap +3 -0
- package/__tests17__/InputSupports/InputSupports.test.jsx +60 -0
- package/__tests17__/Link/Link.test.jsx +63 -0
- package/__tests17__/Link/TextButton.test.jsx +35 -0
- package/__tests17__/List/List.test.jsx +82 -0
- package/__tests17__/Modal/Modal.test.jsx +47 -0
- package/__tests17__/Notification/Notification.test.jsx +20 -0
- package/__tests17__/Pagination/Pagination.test.jsx +160 -0
- package/__tests17__/Progress/Progress.test.jsx +79 -0
- package/__tests17__/Radio/Radio.test.jsx +87 -0
- package/__tests17__/Radio/RadioGroup.test.jsx +220 -0
- package/__tests17__/RadioCard/RadioCard.test.jsx +87 -0
- package/__tests17__/RadioCard/RadioCardGroup.test.jsx +246 -0
- package/__tests17__/Search/Search.test.jsx +87 -0
- package/__tests17__/Select/Select.test.jsx +94 -0
- package/__tests17__/SideNav/SideNav.test.jsx +110 -0
- package/__tests17__/Skeleton/Skeleton.test.jsx +61 -0
- package/__tests17__/SkipLink/SkipLink.test.jsx +61 -0
- package/__tests17__/Spacer/Spacer.test.jsx +63 -0
- package/__tests17__/StackView/StackView.test.jsx +211 -0
- package/__tests17__/StackView/StackWrap.test.jsx +47 -0
- package/__tests17__/StackView/getStackedContent.test.jsx +295 -0
- package/__tests17__/StepTracker/StepTracker.test.jsx +108 -0
- package/__tests17__/Tabs/Tabs.test.jsx +49 -0
- package/__tests17__/Tags/Tags.test.jsx +327 -0
- package/__tests17__/TextInput/TextArea.test.jsx +35 -0
- package/__tests17__/TextInput/TextInputBase.test.jsx +125 -0
- package/__tests17__/ThemeProvider/ThemeProvider.test.jsx +80 -0
- package/__tests17__/ThemeProvider/useThemeTokens.test.jsx +514 -0
- package/__tests17__/ThemeProvider/utils/theme-tokens.test.js +41 -0
- package/__tests17__/ToggleSwitch/ToggleSwitch.test.jsx +82 -0
- package/__tests17__/ToggleSwitch/ToggleSwitchGroup.test.jsx +192 -0
- package/__tests17__/Tooltip/Tooltip.test.jsx +65 -0
- package/__tests17__/Tooltip/getTooltipPosition.test.js +79 -0
- package/__tests17__/Typography/typography.test.jsx +90 -0
- package/__tests17__/utils/children.test.jsx +128 -0
- package/__tests17__/utils/containUniqueFields.test.js +25 -0
- package/__tests17__/utils/input.test.js +375 -0
- package/__tests17__/utils/props.test.js +36 -0
- package/__tests17__/utils/semantics.test.jsx +34 -0
- package/__tests17__/utils/useCopy.test.js +42 -0
- package/__tests17__/utils/useResponsiveProp.test.jsx +202 -0
- package/__tests17__/utils/useSpacingScale.test.jsx +273 -0
- package/__tests17__/utils/useUniqueId.test.js +31 -0
- package/component-docs.json +85 -438
- package/lib/A11yInfoProvider/index.js +14 -5
- package/lib/Button/ButtonGroup.js +3 -2
- package/lib/Checkbox/Checkbox.js +9 -6
- package/lib/ExpandCollapse/Control.js +6 -5
- package/lib/ExpandCollapse/Panel.js +5 -4
- package/lib/List/ListItem.js +10 -236
- package/lib/List/ListItemBase.js +162 -0
- package/lib/List/ListItemContent.js +85 -0
- package/lib/List/ListItemMark.js +158 -0
- package/lib/List/PressableListItemBase.js +147 -0
- package/lib/Notification/Notification.js +2 -1
- package/lib/Pagination/Pagination.js +4 -3
- package/lib/Radio/Radio.js +9 -6
- package/lib/RadioCard/RadioCard.js +9 -6
- package/lib/Tabs/Tabs.js +12 -3
- package/lib/Tags/Tags.js +3 -3
- package/lib/TextInput/TextInput.js +5 -4
- package/lib/ViewportProvider/useViewportListener.js +11 -5
- package/lib/utils/hasOwnProperty.js +18 -0
- package/lib/utils/props/a11yProps.js +212 -45
- package/lib/utils/props/getPropSelector.js +47 -5
- package/lib/utils/useResponsiveProp.js +5 -3
- package/lib/utils/withLinkRouter.js +3 -5
- package/lib-module/A11yInfoProvider/index.js +14 -4
- package/lib-module/Button/ButtonGroup.js +3 -2
- package/lib-module/Checkbox/Checkbox.js +9 -6
- package/lib-module/ExpandCollapse/Control.js +6 -5
- package/lib-module/ExpandCollapse/Panel.js +5 -4
- package/lib-module/List/ListItem.js +13 -235
- package/lib-module/List/ListItemBase.js +139 -0
- package/lib-module/List/ListItemContent.js +66 -0
- package/lib-module/List/ListItemMark.js +143 -0
- package/lib-module/List/PressableListItemBase.js +117 -0
- package/lib-module/Notification/Notification.js +2 -1
- package/lib-module/Pagination/Pagination.js +5 -3
- package/lib-module/Radio/Radio.js +9 -6
- package/lib-module/RadioCard/RadioCard.js +9 -6
- package/lib-module/Tabs/Tabs.js +13 -4
- package/lib-module/Tags/Tags.js +3 -3
- package/lib-module/TextInput/TextInput.js +5 -4
- package/lib-module/ViewportProvider/useViewportListener.js +10 -4
- package/lib-module/utils/hasOwnProperty.js +11 -0
- package/lib-module/utils/props/a11yProps.js +210 -45
- package/lib-module/utils/props/getPropSelector.js +44 -5
- package/lib-module/utils/useResponsiveProp.js +3 -4
- package/lib-module/utils/withLinkRouter.js +3 -5
- package/package.json +11 -16
- package/src/A11yInfoProvider/index.jsx +20 -4
- package/src/Button/ButtonGroup.jsx +4 -2
- package/src/Checkbox/Checkbox.jsx +7 -3
- package/src/ExpandCollapse/Control.jsx +8 -5
- package/src/ExpandCollapse/Panel.jsx +7 -5
- package/src/List/ListItem.jsx +12 -191
- package/src/List/ListItemBase.jsx +118 -0
- package/src/List/ListItemContent.jsx +52 -0
- package/src/List/ListItemMark.jsx +99 -0
- package/src/List/PressableListItemBase.jsx +102 -0
- package/src/Notification/Notification.jsx +1 -1
- package/src/Pagination/Pagination.jsx +6 -1
- package/src/Radio/Radio.jsx +7 -3
- package/src/RadioCard/RadioCard.jsx +7 -3
- package/src/Tabs/Tabs.jsx +19 -2
- package/src/Tags/Tags.jsx +3 -3
- package/src/TextInput/TextInput.jsx +4 -4
- package/src/ViewportProvider/useViewportListener.js +10 -5
- package/src/utils/hasOwnProperty.js +11 -0
- package/src/utils/props/a11yProps.js +168 -55
- package/src/utils/props/getPropSelector.js +45 -4
- package/src/utils/useResponsiveProp.js +3 -3
- package/src/utils/withLinkRouter.jsx +1 -3
|
@@ -1,9 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
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 =>
|
|
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';
|
|
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
|
|
30
|
-
"@testing-library/react-native": "
|
|
31
|
-
"react-test-renderer": "
|
|
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.
|
|
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.
|
|
69
|
+
"version": "1.15.0"
|
|
75
70
|
}
|
|
@@ -16,8 +16,14 @@ const A11yInfoProvider = ({ children }) => {
|
|
|
16
16
|
return () => {}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
AccessibilityInfo.addEventListener(
|
|
20
|
-
|
|
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
|
|
42
|
-
|
|
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
|
-
{...
|
|
145
|
-
|
|
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
|
-
|
|
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({
|
|
60
|
-
|
|
61
|
-
...
|
|
62
|
-
|
|
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(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
package/src/List/ListItem.jsx
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|