@telus-uds/components-base 0.0.2-prerelease.6 → 0.0.2-prerelease.7

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 (155) hide show
  1. package/.ultra.cache.json +1 -1
  2. package/CHANGELOG.md +20 -0
  3. package/__fixtures__/testTheme.js +424 -37
  4. package/__tests__/Button/ButtonBase.test.jsx +2 -31
  5. package/__tests__/Checkbox/Checkbox.test.jsx +94 -0
  6. package/__tests__/InputSupports/InputSupports.test.jsx +50 -0
  7. package/__tests__/List/List.test.jsx +60 -0
  8. package/__tests__/Radio/Radio.test.jsx +87 -0
  9. package/__tests__/Select/Select.test.jsx +93 -0
  10. package/__tests__/Skeleton/Skeleton.test.jsx +61 -0
  11. package/__tests__/Tags/Tags.test.jsx +328 -0
  12. package/__tests__/TextInput/TextArea.test.jsx +34 -0
  13. package/__tests__/TextInput/{TextInput.test.jsx → TextInputBase.test.jsx} +20 -46
  14. package/jest.config.js +3 -1
  15. package/lib/Button/Button.js +10 -3
  16. package/lib/Button/ButtonBase.js +73 -59
  17. package/lib/Button/ButtonGroup.js +11 -27
  18. package/lib/Button/ButtonLink.js +5 -0
  19. package/lib/Checkbox/Checkbox.js +308 -0
  20. package/lib/Checkbox/CheckboxInput.native.js +6 -0
  21. package/lib/Checkbox/CheckboxInput.web.js +57 -0
  22. package/lib/Checkbox/index.js +2 -0
  23. package/lib/Feedback/Feedback.js +20 -3
  24. package/lib/Icon/Icon.js +8 -5
  25. package/lib/Icon/IconText.js +72 -0
  26. package/lib/Icon/index.js +2 -1
  27. package/lib/InputSupports/InputSupports.js +90 -0
  28. package/lib/InputSupports/index.js +2 -0
  29. package/lib/InputSupports/propTypes.js +55 -0
  30. package/lib/Link/ChevronLink.js +23 -20
  31. package/lib/Link/InlinePressable.native.js +78 -0
  32. package/lib/Link/InlinePressable.web.js +32 -0
  33. package/lib/Link/Link.js +11 -10
  34. package/lib/Link/LinkBase.js +62 -123
  35. package/lib/Link/TextButton.js +20 -9
  36. package/lib/Link/index.js +2 -1
  37. package/lib/List/List.js +52 -0
  38. package/lib/List/ListItem.js +207 -0
  39. package/lib/List/index.js +2 -0
  40. package/lib/Pagination/PageButton.js +2 -25
  41. package/lib/Pagination/SideButton.js +27 -37
  42. package/lib/Radio/Radio.js +291 -0
  43. package/lib/Radio/RadioInput.native.js +6 -0
  44. package/lib/Radio/RadioInput.web.js +59 -0
  45. package/lib/Radio/index.js +2 -0
  46. package/lib/Select/Group.native.js +14 -0
  47. package/lib/Select/Group.web.js +18 -0
  48. package/lib/Select/Item.native.js +9 -0
  49. package/lib/Select/Item.web.js +15 -0
  50. package/lib/Select/Picker.native.js +87 -0
  51. package/lib/Select/Picker.web.js +63 -0
  52. package/lib/Select/Select.js +272 -0
  53. package/lib/Select/index.js +6 -0
  54. package/lib/Skeleton/Skeleton.js +119 -0
  55. package/lib/Skeleton/index.js +2 -0
  56. package/lib/Tags/Tags.js +217 -0
  57. package/lib/Tags/index.js +2 -0
  58. package/lib/TextInput/TextArea.js +82 -0
  59. package/lib/TextInput/TextInput.js +23 -304
  60. package/lib/TextInput/TextInputBase.js +229 -0
  61. package/lib/TextInput/index.js +2 -1
  62. package/lib/TextInput/propTypes.js +31 -0
  63. package/lib/ThemeProvider/useThemeTokens.js +54 -3
  64. package/lib/ToggleSwitch/ToggleSwitch.js +1 -1
  65. package/lib/Typography/Typography.js +4 -19
  66. package/lib/index.js +8 -1
  67. package/lib/utils/a11y/index.js +1 -0
  68. package/lib/utils/a11y/textSize.js +33 -0
  69. package/lib/utils/index.js +3 -0
  70. package/lib/utils/info/index.js +7 -0
  71. package/lib/utils/info/platform/index.js +11 -0
  72. package/lib/utils/info/platform/platform.android.js +1 -0
  73. package/lib/utils/info/platform/platform.ios.js +1 -0
  74. package/lib/utils/info/platform/platform.native.js +4 -0
  75. package/lib/utils/info/platform/platform.web.js +1 -0
  76. package/lib/utils/info/versions.js +5 -0
  77. package/lib/utils/pressability.js +92 -0
  78. package/lib/utils/propTypes.js +78 -17
  79. package/package.json +5 -4
  80. package/release-context.json +4 -4
  81. package/src/Button/Button.jsx +6 -3
  82. package/src/Button/ButtonBase.jsx +66 -57
  83. package/src/Button/ButtonGroup.jsx +9 -22
  84. package/src/Button/ButtonLink.jsx +11 -2
  85. package/src/Checkbox/Checkbox.jsx +275 -0
  86. package/src/Checkbox/CheckboxInput.native.jsx +6 -0
  87. package/src/Checkbox/CheckboxInput.web.jsx +55 -0
  88. package/src/Checkbox/index.js +3 -0
  89. package/src/Feedback/Feedback.jsx +13 -4
  90. package/src/Icon/Icon.jsx +9 -5
  91. package/src/Icon/IconText.jsx +63 -0
  92. package/src/Icon/index.js +2 -1
  93. package/src/InputSupports/InputSupports.jsx +86 -0
  94. package/src/InputSupports/index.js +3 -0
  95. package/src/InputSupports/propTypes.js +44 -0
  96. package/src/Link/ChevronLink.jsx +20 -17
  97. package/src/Link/InlinePressable.native.jsx +73 -0
  98. package/src/Link/InlinePressable.web.jsx +37 -0
  99. package/src/Link/Link.jsx +17 -13
  100. package/src/Link/LinkBase.jsx +57 -140
  101. package/src/Link/TextButton.jsx +25 -11
  102. package/src/Link/index.js +2 -1
  103. package/src/List/List.jsx +47 -0
  104. package/src/List/ListItem.jsx +187 -0
  105. package/src/List/index.js +3 -0
  106. package/src/Pagination/PageButton.jsx +2 -16
  107. package/src/Pagination/SideButton.jsx +23 -34
  108. package/src/Radio/Radio.jsx +270 -0
  109. package/src/Radio/RadioInput.native.jsx +6 -0
  110. package/src/Radio/RadioInput.web.jsx +57 -0
  111. package/src/Radio/index.js +3 -0
  112. package/src/Select/Group.native.jsx +14 -0
  113. package/src/Select/Group.web.jsx +15 -0
  114. package/src/Select/Item.native.jsx +10 -0
  115. package/src/Select/Item.web.jsx +11 -0
  116. package/src/Select/Picker.native.jsx +95 -0
  117. package/src/Select/Picker.web.jsx +67 -0
  118. package/src/Select/Select.jsx +265 -0
  119. package/src/Select/index.js +8 -0
  120. package/src/Skeleton/Skeleton.jsx +101 -0
  121. package/src/Skeleton/index.js +3 -0
  122. package/src/Tags/Tags.jsx +206 -0
  123. package/src/Tags/index.js +3 -0
  124. package/src/TextInput/TextArea.jsx +78 -0
  125. package/src/TextInput/TextInput.jsx +17 -284
  126. package/src/TextInput/TextInputBase.jsx +220 -0
  127. package/src/TextInput/index.js +2 -1
  128. package/src/TextInput/propTypes.js +29 -0
  129. package/src/ThemeProvider/useThemeTokens.js +54 -3
  130. package/src/ToggleSwitch/ToggleSwitch.jsx +1 -1
  131. package/src/Typography/Typography.jsx +4 -15
  132. package/src/index.js +8 -1
  133. package/src/utils/a11y/index.js +1 -0
  134. package/src/utils/a11y/textSize.js +30 -0
  135. package/src/utils/index.js +3 -0
  136. package/src/utils/info/index.js +8 -0
  137. package/src/utils/info/platform/index.js +11 -0
  138. package/src/utils/info/platform/platform.android.js +1 -0
  139. package/src/utils/info/platform/platform.ios.js +1 -0
  140. package/src/utils/info/platform/platform.native.js +4 -0
  141. package/src/utils/info/platform/platform.web.js +1 -0
  142. package/src/utils/info/versions.js +6 -0
  143. package/src/utils/pressability.js +92 -0
  144. package/src/utils/propTypes.js +97 -22
  145. package/stories/Button/Button.stories.jsx +5 -0
  146. package/stories/Checkbox/Checkbox.stories.jsx +71 -0
  147. package/stories/Feedback/Feedback.stories.jsx +5 -6
  148. package/stories/Link/Link.stories.jsx +15 -1
  149. package/stories/List/List.stories.jsx +117 -0
  150. package/stories/Radio/Radio.stories.jsx +113 -0
  151. package/stories/Select/Select.stories.jsx +55 -0
  152. package/stories/Skeleton/Skeleton.stories.jsx +36 -0
  153. package/stories/Tags/Tags.stories.jsx +69 -0
  154. package/stories/TextInput/TextArea.stories.jsx +100 -0
  155. package/stories/supports.jsx +1 -1
@@ -57,11 +57,15 @@ const selectIconContainerStyles = ({
57
57
  const Feedback = ({
58
58
  title,
59
59
  children,
60
+ id,
61
+ validation,
60
62
  tokens,
61
63
  variant,
62
64
  ...rest
63
65
  }) => {
64
- const themeTokens = useThemeTokens('Feedback', tokens, variant);
66
+ const themeTokens = useThemeTokens('Feedback', tokens, { ...variant,
67
+ validation
68
+ });
65
69
  const {
66
70
  space
67
71
  } = themeTokens;
@@ -73,10 +77,15 @@ const Feedback = ({
73
77
  const content = typeof children === 'string' ? /*#__PURE__*/React.createElement(Text, {
74
78
  style: contentTextStyles
75
79
  }, children) : children;
76
- const accessibilityProps = a11yProps.select(rest);
80
+ const accessibilityProps = a11yProps.select({
81
+ accessibilityRole: validation === 'error' ? 'alert' : undefined,
82
+ ...rest
83
+ });
77
84
  return /*#__PURE__*/React.createElement(View, Object.assign({
78
85
  style: selectStyles(themeTokens)
79
- }, accessibilityProps), /*#__PURE__*/React.createElement(StackView, {
86
+ }, accessibilityProps, {
87
+ nativeID: id
88
+ }), /*#__PURE__*/React.createElement(StackView, {
80
89
  space: space
81
90
  }, title !== undefined && /*#__PURE__*/React.createElement(View, {
82
91
  style: staticStyles.title
@@ -102,6 +111,14 @@ Feedback.propTypes = {
102
111
  * Feedback content rendered below the title. A render function `({textStyles, variant}) => {}` is supported.
103
112
  */
104
113
  children: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]),
114
+
115
+ /**
116
+ * Use to visually mark the Feedback as valid or invalid.
117
+ */
118
+ validation: PropTypes.oneOf(['error', 'success']),
119
+
120
+ /** @ignore */
121
+ id: PropTypes.string,
105
122
  tokens: getTokensPropType('Feedback'),
106
123
  variant: variantProp.propType
107
124
  };
package/lib/Icon/Icon.js CHANGED
@@ -2,27 +2,30 @@ import React from 'react';
2
2
  import { Platform, View } from 'react-native';
3
3
  import PropTypes from 'prop-types';
4
4
  import { useThemeTokens } from '../ThemeProvider';
5
- import { getTokensPropType, variantProp } from '../utils/propTypes';
5
+ import { getTokensPropType, scaleWithText, variantProp } from '../utils';
6
6
 
7
7
  const Icon = ({
8
8
  IconSvg,
9
9
  variant,
10
10
  label,
11
11
  titleId,
12
- tokens = {}
12
+ tokens,
13
+ scalesWithText = false
13
14
  }) => {
14
15
  const themeTokens = useThemeTokens('Icon', tokens, variant);
16
+ const size = scalesWithText ? scaleWithText(themeTokens.size) : themeTokens.size;
15
17
  const iconContent = /*#__PURE__*/React.createElement(IconSvg, {
16
18
  title: label,
17
19
  titleId: titleId,
18
- size: themeTokens.size,
20
+ size: size,
19
21
  color: themeTokens.color
20
22
  });
21
23
  return Platform.OS === 'web' ? /*#__PURE__*/React.createElement(View // eslint-disable-next-line react-native/no-inline-styles
22
24
  , {
23
25
  style: {
24
- // TODO: https://github.com/telus/universal-design-system/issues/487
25
- transition: 'transform 200ms',
26
+ // TODO: systematise animations.
27
+ // https://github.com/telus/universal-design-system/issues/487
28
+ transition: 'transform 200ms, color 200ms',
26
29
  transform: [themeTokens.scale ? `scale(${themeTokens.scale})` : '', themeTokens.translateX ? `translateX(${themeTokens.translateX}px)` : '', themeTokens.translateY ? `translateY(${themeTokens.translateY}px)` : ''].filter(exists => exists).join(' ')
27
30
  }
28
31
  }, iconContent) : iconContent;
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Platform, View } from 'react-native';
4
+ import { iconComponentPropTypes } from './Icon';
5
+ import { getStackedContent } from '../StackView';
6
+ import { spacingProps } from '../utils';
7
+ /**
8
+ * Returns an icon and some text with a sized gap between them. This is a utility component
9
+ * intended for use when creating components, not intended for use in applications on its own.
10
+ *
11
+ * IconText does not wrap its children, so should be used either:
12
+ * - inline within a <Text> element
13
+ * - within a container with `flexDirection: 'row'`
14
+ */
15
+
16
+ const IconText = ({
17
+ space,
18
+ iconPosition = 'left',
19
+ icon: IconComponent,
20
+ iconProps,
21
+ children
22
+ }) => {
23
+ const iconContent = IconComponent && /*#__PURE__*/React.createElement(IconComponent, Object.assign({
24
+ scalesWithText: true
25
+ }, iconProps)); // Inline images on Android are always baseline-aligned which makes them look misaligned - offset it.
26
+ // See abandoned issue https://github.com/facebook/react-native/issues/6529
27
+
28
+ const size = iconProps?.tokens?.size;
29
+ const iconAdjusted = Platform.OS === 'android' && iconContent && size ? /*#__PURE__*/React.createElement(View, {
30
+ style: {
31
+ transform: [{
32
+ translateY: size * 0.2
33
+ }]
34
+ }
35
+ }, iconContent) : iconContent;
36
+ return getStackedContent(iconPosition === 'left' ? [iconAdjusted, children] : [children, iconAdjusted], {
37
+ space,
38
+ direction: 'row'
39
+ });
40
+ };
41
+
42
+ IconText.propTypes = {
43
+ /**
44
+ * Amount of space to separate the text content and icon. Uses the themes's spacing scale
45
+ * (see useSpacingScale for more info).
46
+ */
47
+ space: spacingProps.types.spacingValue,
48
+
49
+ /**
50
+ * Whether the icon should be to the left of the content or the right of the content.
51
+ */
52
+ iconPosition: PropTypes.oneOf(['left', 'right']),
53
+
54
+ /**
55
+ * A valid UDS icon component imported from a UDS palette.
56
+ */
57
+ icon: PropTypes.func,
58
+
59
+ /**
60
+ * Props that will be passed to the icon component. By default the icon's `scalesWithText`
61
+ * prop will be set as "true" so that the icon continues to match the size of the text
62
+ * if users use accessibility settings to increase text size.
63
+ */
64
+ iconProps: PropTypes.exact(iconComponentPropTypes),
65
+
66
+ /**
67
+ * Content to be rendered alongside the Icon component. Usually this should be a
68
+ * `<Typography>` component, or a component that renders `<Text>`.
69
+ */
70
+ children: PropTypes.node
71
+ };
72
+ export default IconText;
package/lib/Icon/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  import Icon, { iconComponentPropTypes, iconSvgPropTypes } from './Icon';
2
+ import IconText from './IconText';
2
3
  export default Icon;
3
- export { iconComponentPropTypes, iconSvgPropTypes };
4
+ export { iconComponentPropTypes, iconSvgPropTypes, IconText };
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import InputLabel from '../InputLabel';
4
+ import Feedback from '../Feedback';
5
+ import StackView from '../StackView';
6
+ import { useThemeTokens } from '../ThemeProvider';
7
+ import useUniqueId from '../utils/useUniqueId';
8
+
9
+ const joinDefined = array => array.filter(item => item !== undefined).join(' ');
10
+
11
+ function InputSupports({
12
+ children,
13
+ label,
14
+ hint,
15
+ hintPosition = 'inline',
16
+ feedback,
17
+ tooltip,
18
+ validation
19
+ }) {
20
+ const {
21
+ space
22
+ } = useThemeTokens('InputSupports');
23
+ const hasValidationError = validation === 'error';
24
+ const inputId = useUniqueId('input');
25
+ const hintId = useUniqueId('input-hint');
26
+ const feedbackId = useUniqueId('input-feedback');
27
+ const a11yProps = {
28
+ accessibilityLabel: label,
29
+ accessibilityHint: joinDefined([!hasValidationError && feedback, hint]),
30
+ // native only -> replaced with describedBy on web
31
+ accessibilityDescribedBy: joinDefined([!hasValidationError && feedback && feedbackId, // feedback receives a11yRole=alert on error, so there's no need to include it here
32
+ hint && hintId]),
33
+ // introduced in RNW 0.15.0
34
+ accessibilityInvalid: hasValidationError // introduced in RNW 0.15.0
35
+
36
+ };
37
+ return /*#__PURE__*/React.createElement(StackView, {
38
+ space: space
39
+ }, label && /*#__PURE__*/React.createElement(InputLabel, {
40
+ label: label,
41
+ hint: hint,
42
+ hintPosition: hintPosition,
43
+ hintId: hintId,
44
+ tooltip: tooltip,
45
+ forId: inputId
46
+ }), typeof children === 'function' ? children({
47
+ a11yProps,
48
+ inputId
49
+ }) : children, feedback && /*#__PURE__*/React.createElement(Feedback, {
50
+ id: feedbackId,
51
+ title: feedback,
52
+ validation: validation
53
+ }));
54
+ }
55
+
56
+ InputSupports.propTypes = {
57
+ children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
58
+
59
+ /**
60
+ * The input label.
61
+ */
62
+ label: PropTypes.string,
63
+
64
+ /**
65
+ * A short description of the expected input.
66
+ */
67
+ hint: PropTypes.string,
68
+
69
+ /**
70
+ * Position of the hint relative to label.
71
+ */
72
+ hintPosition: PropTypes.oneOf(['inline', 'below']),
73
+
74
+ /**
75
+ * A detailed description of validation error/success or additional instructions.
76
+ * Visual variant is determined based on the `validation` prop.
77
+ */
78
+ feedback: PropTypes.string,
79
+
80
+ /**
81
+ * Content of an optional `Tooltip`. If set, a tooltip button will be shown next to the label.
82
+ */
83
+ tooltip: PropTypes.string,
84
+
85
+ /**
86
+ * Use to visually mark an input as valid or invalid.
87
+ */
88
+ validation: PropTypes.oneOf(['error', 'success'])
89
+ };
90
+ export default InputSupports;
@@ -0,0 +1,2 @@
1
+ import InputSupports from './InputSupports';
2
+ export default InputSupports;
@@ -0,0 +1,55 @@
1
+ import PropTypes from 'prop-types';
2
+ const inputSupportProps = {
3
+ types: {
4
+ /**
5
+ * The input label.
6
+ */
7
+ label: PropTypes.string,
8
+
9
+ /**
10
+ * A short description of the expected input.
11
+ */
12
+ hint: PropTypes.string,
13
+
14
+ /**
15
+ * Position of the hint relative to label. Use `below` to display a larger hint below the label.
16
+ */
17
+ hintPosition: PropTypes.oneOf(['inline', 'below']),
18
+
19
+ /**
20
+ * A detailed description of validation error/success or additional instructions.
21
+ * Visual variant is determined based on the `validation` prop.
22
+ */
23
+ feedback: PropTypes.string,
24
+
25
+ /**
26
+ * Content of an optional `Tooltip`. If set, a tooltip button will be shown next to the label.
27
+ */
28
+ tooltip: PropTypes.string,
29
+
30
+ /**
31
+ * Use to visually mark an input as valid or invalid.
32
+ */
33
+ validation: PropTypes.oneOf(['error', 'success'])
34
+ },
35
+ select: ({
36
+ label,
37
+ hint,
38
+ hintPosition,
39
+ feedback,
40
+ tooltip,
41
+ validation,
42
+ ...rest
43
+ }) => ({
44
+ props: {
45
+ label,
46
+ hint,
47
+ hintPosition,
48
+ feedback,
49
+ tooltip,
50
+ validation
51
+ },
52
+ rest
53
+ })
54
+ };
55
+ export default inputSupportProps;
@@ -1,43 +1,46 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { useThemeTokens } from '../ThemeProvider';
4
- import Link from './Link';
3
+ import { useThemeTokensCallback } from '../ThemeProvider';
4
+ import { selectTokens, getTokensPropType } from '../utils';
5
+ import LinkBase from './LinkBase';
5
6
  /**
6
7
  * `ChevronLink` is a convenience wrapper around the `Link` component to enable "directional" links.
7
8
  * It effectively pre-binds left and right icons, and a directional translation of the icon on hover.
8
9
  *
9
- * ChevronLink is not intended to be deeply themable. If you have a use case outside of the above you are better off constructing theme variants of `Link`.
10
- * In that vein, note that the `tokens` and `variant` system props for `ChevronLink` are passed directly to the underlying `Link` component: ChevronLink itself does not support further customisation using these system props.
10
+ * ChevronLink is not intended to be deeply themable; variants passed to ChevronLink are forwarded to Link.
11
11
  */
12
12
 
13
13
  const ChevronLink = ({
14
14
  direction = 'right',
15
15
  children,
16
16
  tokens,
17
+ variant,
17
18
  ...linkProps
18
19
  }) => {
19
- const {
20
- iconDisplace,
21
- leftIcon,
22
- rightIcon
23
- } = useThemeTokens('ChevronLink', {}, {});
24
- const icon = direction === 'right' ? rightIcon : leftIcon;
20
+ const getChevronTokens = useThemeTokensCallback('ChevronLink', tokens, variant);
25
21
 
26
- const tokensCallback = linkState => ({ ...(typeof tokens === 'function' ? tokens(linkState) : tokens),
27
- iconGapBefore: 0,
28
- iconGapAfter: 0,
29
- iconTranslateX: linkState.hover ? iconDisplace * (direction === 'right' ? 1 : -1) : 0
30
- });
22
+ const applyChevronTokens = linkState => {
23
+ const {
24
+ leftIcon,
25
+ rightIcon,
26
+ iconDisplace,
27
+ ...otherTokens
28
+ } = getChevronTokens(linkState);
29
+ return { ...selectTokens('Link', otherTokens),
30
+ icon: direction === 'right' ? rightIcon : leftIcon,
31
+ iconTranslateX: iconDisplace * (direction === 'right' ? 1 : -1) || 0
32
+ };
33
+ };
31
34
 
32
- return /*#__PURE__*/React.createElement(Link, Object.assign({
35
+ const getTokens = useThemeTokensCallback('Link', applyChevronTokens, variant);
36
+ return /*#__PURE__*/React.createElement(LinkBase, Object.assign({}, linkProps, {
33
37
  iconPosition: direction,
34
- icon: icon
35
- }, linkProps, {
36
- tokens: tokensCallback
38
+ tokens: getTokens
37
39
  }), children);
38
40
  };
39
41
 
40
- ChevronLink.propTypes = { ...Link.propTypes,
42
+ ChevronLink.propTypes = { ...LinkBase.propTypes,
43
+ tokens: getTokensPropType('ChevronLink', 'Link'),
41
44
  direction: PropTypes.oneOf(['left', 'right'])
42
45
  };
43
46
  export default ChevronLink;
@@ -0,0 +1,78 @@
1
+ /* eslint-disable camelcase */
2
+ import React, { useState } from 'react';
3
+ import { Text, TouchableWithoutFeedback } from 'react-native';
4
+ /**
5
+ * @typedef {import('react-native').PressableProps} PressableProps
6
+ */
7
+ // TouchableWithoutFeedback and Pressable have similar but not identical props APIs
8
+
9
+ const pressablePropsToTouchable = ({
10
+ unstable_pressDelay,
11
+ android_disableSound,
12
+ android_ripple,
13
+ // Unsupported, discard it
14
+ ...props
15
+ }) => ({ ...props,
16
+ touchSoundDisabled: android_disableSound,
17
+ delayPressIn: unstable_pressDelay
18
+ });
19
+ /**
20
+ * InlinePressable is an alternative to React Native's Pressable that works better when nested
21
+ * inline inside Text. It accepts the same props as React Native's Pressable.
22
+ *
23
+ * There are a lot of React Native bugs around Views/Pressables nested in Text, e.g.:
24
+ *
25
+ * - https://github.com/facebook/react-native/issues/23601#issuecomment-468069822
26
+ * - https://github.com/facebook/react-native/issues/30375
27
+ * - https://github.com/facebook/react-native/issues/31955
28
+ *
29
+ * On Native, these can be avoided using a `Text` wrapped in a `TouchableWithoutFeedback`; the latter
30
+ * injects additional handlers such as onPressIn to `Text`, resulting in a tree that is purely `Text`.
31
+ *
32
+ * Note that this should only be used on Native, not Web, because React Navigation's Web navigation
33
+ * functions don't work in Touchables (but do work in Pressable) due to different event handling.
34
+ *
35
+ * @param {PressableProps} PressableProps
36
+ */
37
+ // React Native exports prop Types but not propTypes, use JSDoc types here rather than duplicate RN
38
+ // eslint-disable-next-line react/prop-types
39
+
40
+
41
+ const InlinePressable = ({
42
+ onPress,
43
+ children,
44
+ style,
45
+ ...rest
46
+ }) => {
47
+ const [isFocused, setIsFocused] = useState(false);
48
+
49
+ const handleFocus = () => setIsFocused(true);
50
+
51
+ const handleBlur = () => setIsFocused(false);
52
+
53
+ const [isPressed, setIsPressed] = useState(false);
54
+
55
+ const handlePressIn = () => setIsPressed(true);
56
+
57
+ const handlePressOut = () => setIsPressed(false);
58
+
59
+ const pressState = {
60
+ pressed: isPressed,
61
+ focus: isFocused,
62
+ // limited support on native
63
+ hover: false // not yet supported on native
64
+
65
+ };
66
+ const currentStyle = typeof style === 'function' ? style(pressState) : style;
67
+ return /*#__PURE__*/React.createElement(TouchableWithoutFeedback, Object.assign({
68
+ onPress: onPress,
69
+ onPressIn: handlePressIn,
70
+ onPressOut: handlePressOut,
71
+ onFocus: handleFocus,
72
+ onBlur: handleBlur
73
+ }, pressablePropsToTouchable(rest)), /*#__PURE__*/React.createElement(Text, {
74
+ style: currentStyle
75
+ }, typeof children === 'function' ? children(pressState) : children));
76
+ };
77
+
78
+ export default InlinePressable;
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { Pressable, StyleSheet } from 'react-native';
3
+ /**
4
+ * @typedef {import('react-native').PressableProps} PressableProps
5
+ */
6
+
7
+ /**
8
+ * InlinePressable is an alternative to React Native's Pressable that works better when nested
9
+ * inline inside Text. It accepts the same props as React Native's Pressable.
10
+ *
11
+ * On Web it simply passes its props to Pressable and defaults to `inline-flex` instead of `flex`.
12
+ *
13
+ * @param {PressableProps} PressableProps
14
+ */
15
+ // React Native exports prop Types but not propTypes, use JSDoc types here rather than duplicate RN
16
+ // eslint-disable-next-line react/prop-types
17
+
18
+ const InlinePressable = ({
19
+ children,
20
+ style,
21
+ ...props
22
+ }) => /*#__PURE__*/React.createElement(Pressable, Object.assign({
23
+ style: pressState => [staticStyles.inline, typeof style === 'function' ? style(pressState) : style]
24
+ }, props), pressState => typeof children === 'function' ? children(pressState) : children);
25
+
26
+ const staticStyles = StyleSheet.create({
27
+ inline: {
28
+ // Stop Pressable defaulting to (block) flex
29
+ display: 'inline-flex'
30
+ }
31
+ });
32
+ export default InlinePressable;
package/lib/Link/Link.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { useThemeTokensCallback } from '../ThemeProvider';
2
3
  import LinkBase from './LinkBase';
3
4
 
4
5
  const Link = ({
@@ -6,16 +7,16 @@ const Link = ({
6
7
  children,
7
8
  accessibilityRole = 'link',
8
9
  variant = {},
10
+ tokens,
9
11
  ...linkProps
10
- }) => /*#__PURE__*/React.createElement(LinkBase, Object.assign({
11
- href: href,
12
- accessibilityRole: accessibilityRole,
13
- variant: {
14
- component: 'Link',
15
- ...variant
16
- }
17
- }, linkProps), children);
18
-
19
- Link.propTypes = { ...LinkBase.propTypes
12
+ }) => {
13
+ const getTokens = useThemeTokensCallback('Link', tokens, variant);
14
+ return /*#__PURE__*/React.createElement(LinkBase, Object.assign({
15
+ href: href,
16
+ accessibilityRole: accessibilityRole,
17
+ tokens: getTokens
18
+ }, linkProps), children);
20
19
  };
20
+
21
+ Link.propTypes = LinkBase.propTypes;
21
22
  export default Link;