@telus-uds/components-base 3.22.0 → 3.24.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 (84) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/lib/cjs/Button/Button.js +2 -0
  3. package/lib/cjs/Button/ButtonBase.js +10 -5
  4. package/lib/cjs/Button/ButtonDropdown.js +2 -0
  5. package/lib/cjs/Button/ButtonGroup.js +45 -38
  6. package/lib/cjs/Button/propTypes.js +6 -0
  7. package/lib/cjs/Card/CardBase.js +97 -17
  8. package/lib/cjs/Card/PressableCardBase.js +12 -8
  9. package/lib/cjs/Carousel/Carousel.js +52 -19
  10. package/lib/cjs/Carousel/CarouselItem/CarouselItem.js +23 -3
  11. package/lib/cjs/HorizontalScroll/HorizontalScroll.js +5 -2
  12. package/lib/cjs/Icon/Icon.js +11 -11
  13. package/lib/cjs/Icon/IconText.js +0 -1
  14. package/lib/cjs/Listbox/GroupControl.js +44 -44
  15. package/lib/cjs/Listbox/Listbox.js +63 -20
  16. package/lib/cjs/Listbox/ListboxGroup.js +141 -9
  17. package/lib/cjs/Listbox/ListboxOverlay.js +13 -5
  18. package/lib/cjs/Listbox/PressableItem.js +8 -4
  19. package/lib/cjs/Listbox/SecondLevelHeader.js +201 -0
  20. package/lib/cjs/Listbox/dictionary.js +14 -0
  21. package/lib/cjs/Shortcuts/Shortcuts.js +169 -0
  22. package/lib/cjs/Shortcuts/ShortcutsItem.js +280 -0
  23. package/lib/cjs/Shortcuts/index.js +16 -0
  24. package/lib/cjs/TextInput/TextInputBase.js +5 -1
  25. package/lib/cjs/Tooltip/Tooltip.native.js +2 -0
  26. package/lib/cjs/Validator/Validator.js +171 -135
  27. package/lib/cjs/index.js +15 -0
  28. package/lib/esm/Button/Button.js +2 -0
  29. package/lib/esm/Button/ButtonBase.js +10 -5
  30. package/lib/esm/Button/ButtonDropdown.js +2 -0
  31. package/lib/esm/Button/ButtonGroup.js +44 -39
  32. package/lib/esm/Button/propTypes.js +6 -0
  33. package/lib/esm/Card/CardBase.js +97 -17
  34. package/lib/esm/Card/PressableCardBase.js +10 -8
  35. package/lib/esm/Carousel/Carousel.js +52 -19
  36. package/lib/esm/Carousel/CarouselItem/CarouselItem.js +23 -3
  37. package/lib/esm/HorizontalScroll/HorizontalScroll.js +6 -3
  38. package/lib/esm/Icon/Icon.js +11 -11
  39. package/lib/esm/Icon/IconText.js +0 -1
  40. package/lib/esm/Listbox/GroupControl.js +44 -44
  41. package/lib/esm/Listbox/Listbox.js +64 -21
  42. package/lib/esm/Listbox/ListboxGroup.js +143 -11
  43. package/lib/esm/Listbox/ListboxOverlay.js +13 -5
  44. package/lib/esm/Listbox/PressableItem.js +8 -4
  45. package/lib/esm/Listbox/SecondLevelHeader.js +194 -0
  46. package/lib/esm/Listbox/dictionary.js +8 -0
  47. package/lib/esm/Shortcuts/Shortcuts.js +160 -0
  48. package/lib/esm/Shortcuts/ShortcutsItem.js +273 -0
  49. package/lib/esm/Shortcuts/index.js +3 -0
  50. package/lib/esm/TextInput/TextInputBase.js +5 -1
  51. package/lib/esm/Tooltip/Tooltip.native.js +2 -0
  52. package/lib/esm/Validator/Validator.js +171 -135
  53. package/lib/esm/index.js +1 -0
  54. package/lib/package.json +2 -2
  55. package/package.json +2 -2
  56. package/src/Button/Button.jsx +2 -1
  57. package/src/Button/ButtonBase.jsx +18 -12
  58. package/src/Button/ButtonDropdown.jsx +2 -0
  59. package/src/Button/ButtonGroup.jsx +62 -45
  60. package/src/Button/propTypes.js +6 -0
  61. package/src/Card/CardBase.jsx +113 -14
  62. package/src/Card/PressableCardBase.jsx +17 -5
  63. package/src/Carousel/Carousel.jsx +58 -5
  64. package/src/Carousel/CarouselItem/CarouselItem.jsx +31 -3
  65. package/src/HorizontalScroll/HorizontalScroll.jsx +6 -3
  66. package/src/Icon/Icon.jsx +14 -14
  67. package/src/Icon/IconText.jsx +0 -1
  68. package/src/Listbox/GroupControl.jsx +72 -70
  69. package/src/Listbox/Listbox.jsx +67 -11
  70. package/src/Listbox/ListboxGroup.jsx +160 -27
  71. package/src/Listbox/ListboxOverlay.jsx +23 -5
  72. package/src/Listbox/PressableItem.jsx +8 -4
  73. package/src/Listbox/SecondLevelHeader.jsx +182 -0
  74. package/src/Listbox/dictionary.js +8 -0
  75. package/src/Shortcuts/Shortcuts.jsx +174 -0
  76. package/src/Shortcuts/ShortcutsItem.jsx +297 -0
  77. package/src/Shortcuts/index.js +4 -0
  78. package/src/TextInput/TextInputBase.jsx +5 -1
  79. package/src/Tooltip/Tooltip.native.jsx +2 -1
  80. package/src/Validator/Validator.jsx +180 -159
  81. package/src/index.js +1 -0
  82. package/types/Listbox.d.ts +24 -0
  83. package/types/Shortcuts.d.ts +136 -0
  84. package/types/index.d.ts +12 -0
@@ -0,0 +1,182 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { View, StyleSheet, Text, Pressable } from 'react-native'
4
+ import { useThemeTokens } from '../ThemeProvider'
5
+ import { useCopy, variantProp, copyPropTypes } from '../utils'
6
+ import Icon from '../Icon'
7
+ import IconButton from '../IconButton'
8
+ import Divider from '../Divider'
9
+ import defaultDictionary from './dictionary'
10
+
11
+ const styles = StyleSheet.create({
12
+ headerContainer: {
13
+ width: '100%'
14
+ },
15
+ headerContent: {
16
+ flexDirection: 'row',
17
+ alignItems: 'center',
18
+ width: '100%'
19
+ },
20
+ leftSection: {
21
+ flexDirection: 'row',
22
+ alignItems: 'center',
23
+ flex: 1
24
+ },
25
+ backIcon: {
26
+ marginRight: 8,
27
+ flexShrink: 0
28
+ },
29
+ labelText: {
30
+ flex: 1
31
+ },
32
+ closeButton: {
33
+ flexShrink: 0
34
+ },
35
+ dividerContainer: {
36
+ width: '100%'
37
+ }
38
+ })
39
+
40
+ const selectHeaderContainerStyles = ({ secondLevelHeaderBackgroundColor }) => ({
41
+ backgroundColor: secondLevelHeaderBackgroundColor
42
+ })
43
+
44
+ const selectHeaderContentStyles = ({
45
+ secondLevelHeaderPaddingTop,
46
+ secondLevelHeaderPaddingBottom,
47
+ secondLevelHeaderPaddingLeft,
48
+ secondLevelHeaderPaddingRight
49
+ }) => ({
50
+ paddingTop: secondLevelHeaderPaddingTop,
51
+ paddingBottom: secondLevelHeaderPaddingBottom,
52
+ paddingLeft: secondLevelHeaderPaddingLeft,
53
+ paddingRight: secondLevelHeaderPaddingRight
54
+ })
55
+
56
+ const selectLabelTextStyles = ({
57
+ secondLevelBackLinkFontName,
58
+ secondLevelBackLinkFontWeight,
59
+ secondLevelBackLinkFontSize,
60
+ secondLevelBackLinkColor
61
+ }) => ({
62
+ fontFamily: `${secondLevelBackLinkFontName}${secondLevelBackLinkFontWeight}normal`,
63
+ fontSize: secondLevelBackLinkFontSize,
64
+ color: secondLevelBackLinkColor
65
+ })
66
+
67
+ /**
68
+ * SecondLevelHeader component for Listbox secondLevel variant.
69
+ * Displays a header with back button icon, title text, and close button (IconButton),
70
+ * separated from content by a Divider.
71
+ */
72
+ const SecondLevelHeader = React.forwardRef(
73
+ (
74
+ {
75
+ label,
76
+ onBack,
77
+ onClose,
78
+ copy = 'en',
79
+ dictionary = defaultDictionary,
80
+ tokens: tokensProp = {},
81
+ variant = {}
82
+ },
83
+ ref
84
+ ) => {
85
+ const tokens = useThemeTokens('Listbox', variant, tokensProp)
86
+ const getCopy = useCopy({ dictionary, copy })
87
+
88
+ const {
89
+ secondLevelBackIcon,
90
+ secondLevelBackIconColor,
91
+ secondLevelCloseIcon,
92
+ secondLevelCloseIconSize,
93
+ secondLevelCloseButtonBorderWidth,
94
+ secondLevelCloseButtonPadding,
95
+ secondLevelDividerColor,
96
+ secondLevelDividerWidth
97
+ } = tokens
98
+
99
+ return (
100
+ <View style={[styles.headerContainer, selectHeaderContainerStyles(tokens)]} ref={ref}>
101
+ <View style={[styles.headerContent, selectHeaderContentStyles(tokens)]}>
102
+ <Pressable onPress={onBack} style={styles.leftSection}>
103
+ <View style={styles.backIcon}>
104
+ <Icon
105
+ icon={secondLevelBackIcon}
106
+ tokens={{
107
+ color: secondLevelBackIconColor
108
+ }}
109
+ variant={{ size: 'micro' }}
110
+ />
111
+ </View>
112
+ <Text numberOfLines={1} style={[styles.labelText, selectLabelTextStyles(tokens)]}>
113
+ {label}
114
+ </Text>
115
+ </Pressable>
116
+ <View style={styles.closeButton}>
117
+ <IconButton
118
+ icon={secondLevelCloseIcon}
119
+ onPress={onClose}
120
+ accessibilityLabel={getCopy('closeMenu')}
121
+ tokens={{
122
+ iconSize: secondLevelCloseIconSize,
123
+ borderWidth: secondLevelCloseButtonBorderWidth,
124
+ padding: secondLevelCloseButtonPadding
125
+ }}
126
+ />
127
+ </View>
128
+ </View>
129
+ <View style={styles.dividerContainer}>
130
+ <Divider
131
+ tokens={{
132
+ color: secondLevelDividerColor,
133
+ width: secondLevelDividerWidth
134
+ }}
135
+ />
136
+ </View>
137
+ </View>
138
+ )
139
+ }
140
+ )
141
+
142
+ SecondLevelHeader.displayName = 'SecondLevelHeader'
143
+
144
+ SecondLevelHeader.propTypes = {
145
+ /**
146
+ * The label text to display (typically the parent item label)
147
+ */
148
+ label: PropTypes.string.isRequired,
149
+ /**
150
+ * Callback when back button is clicked
151
+ */
152
+ onBack: PropTypes.func.isRequired,
153
+ /**
154
+ * Callback when close button is clicked
155
+ */
156
+ onClose: PropTypes.func.isRequired,
157
+ /**
158
+ * Select English or French copy
159
+ */
160
+ copy: copyPropTypes,
161
+ /**
162
+ * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
163
+ */
164
+ dictionary: PropTypes.shape({
165
+ en: PropTypes.shape({
166
+ closeMenu: PropTypes.string.isRequired
167
+ }),
168
+ fr: PropTypes.shape({
169
+ closeMenu: PropTypes.string.isRequired
170
+ })
171
+ }),
172
+ /**
173
+ * Custom tokens to override theme tokens
174
+ */
175
+ tokens: PropTypes.object,
176
+ /**
177
+ * Variant configuration
178
+ */
179
+ variant: variantProp.propType
180
+ }
181
+
182
+ export default SecondLevelHeader
@@ -0,0 +1,8 @@
1
+ export default {
2
+ en: {
3
+ closeMenu: 'Close menu'
4
+ },
5
+ fr: {
6
+ closeMenu: 'Fermer le menu'
7
+ }
8
+ }
@@ -0,0 +1,174 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { Platform, StyleSheet, View } from 'react-native'
4
+ import { viewports } from '@telus-uds/system-constants'
5
+
6
+ import { useTheme, useThemeTokens } from '../ThemeProvider'
7
+ import { useViewport } from '../ViewportProvider'
8
+ import {
9
+ a11yProps,
10
+ getTokensPropType,
11
+ selectSystemProps,
12
+ useResponsiveProp,
13
+ variantProp,
14
+ viewProps
15
+ } from '../utils'
16
+
17
+ import HorizontalScroll, {
18
+ horizontalScrollUtils,
19
+ HorizontalScrollButton
20
+ } from '../HorizontalScroll'
21
+
22
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
23
+
24
+ const { selectHorizontalScrollTokens, useItemPositions } = horizontalScrollUtils
25
+
26
+ const selectStyles = (themeTokens, maxWidth, viewport) => {
27
+ const isDesktop =
28
+ viewport === viewports.md || viewport === viewports.lg || viewport === viewports.xl
29
+
30
+ return {
31
+ wrapper: {
32
+ alignItems: isDesktop ? 'center' : 'flex-start'
33
+ },
34
+ scrollContainer: {
35
+ width: '100%',
36
+ ...(isDesktop && { maxWidth })
37
+ },
38
+ container: {
39
+ paddingTop: themeTokens.mainContainerTopPadding,
40
+ paddingBottom: themeTokens.mainContainerBottomPadding,
41
+ paddingLeft: themeTokens.mainContainerLeftPadding,
42
+ paddingRight: themeTokens.mainContainerRightPadding,
43
+ gap: themeTokens.mainContainerGap,
44
+ ...(isDesktop && {
45
+ alignItems: 'flex-start',
46
+ justifyContent: 'center'
47
+ })
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * A horizontal scrollable shortcuts component that displays a collection of shortcut items.
54
+ * This component automatically injects shared configuration props to all ShortcutsItem children
55
+ * via React.cloneElement, including variant settings, hideLabels, and iconVariant.
56
+ *
57
+ * @component
58
+ * @param {Object} props - Component properties
59
+ * @param {Object} [props.tokens] - Theme tokens to customize the component's appearance
60
+ * @param {Object} [props.variant] - Visual variant configuration for the shortcuts container and its items
61
+ * @param {string} [props.variant.width] - Width variant to apply to all items (e.g., 'equal', 'dynamic')
62
+ * @param {Object} [props.scrollButtonTokens] - Tokens to customize scroll button appearance
63
+ * @param {boolean} [props.hideLabels=false] - Whether to hide labels on all shortcut items (can be overridden per item)
64
+ * @param {Object} [props.iconVariant] - Icon variant to apply to all shortcut items (can be overridden per item)
65
+ * @param {React.ReactNode} props.children - ShortcutsItem components to render
66
+ * @param {React.Ref} ref - Forwarded ref to the component's root element
67
+ * @returns {React.ReactElement} Rendered shortcuts component with horizontal scroll functionality
68
+ *
69
+ * @example
70
+ * <Shortcuts hideLabels={false} variant={{ width: 'equal' }}>
71
+ * <ShortcutsItem icon={HomeIcon} label="Home" href="/home" />
72
+ * <ShortcutsItem icon={SettingsIcon} label="Settings" href="/settings" />
73
+ * </Shortcuts>
74
+ *
75
+ * @example
76
+ * // Item-level props override container props
77
+ * <Shortcuts hideLabels iconVariant={{ size: 'small' }}>
78
+ * <ShortcutsItem icon={HomeIcon} label="Home" hideLabel={false} />
79
+ * <ShortcutsItem icon={SettingsIcon} label="Settings" />
80
+ * </Shortcuts>
81
+ */
82
+ const Shortcuts = React.forwardRef(
83
+ (
84
+ { tokens, variant, scrollButtonTokens, hideLabels = false, iconVariant, children, ...rest },
85
+ ref
86
+ ) => {
87
+ const viewport = useViewport()
88
+ const themeTokens = useThemeTokens('Shortcuts', tokens, variant, {
89
+ viewport
90
+ })
91
+
92
+ const { themeOptions } = useTheme()
93
+ const maxWidth = useResponsiveProp(
94
+ themeOptions?.contentMaxWidth,
95
+ viewports.map.get(viewports.xl)
96
+ )
97
+
98
+ const [itemPositions] = useItemPositions()
99
+
100
+ const [maxItemWidth, setMaxItemWidth] = React.useState(null)
101
+
102
+ const registerWidth = React.useCallback(
103
+ (width) => setMaxItemWidth((prev) => (prev == null || width > prev ? width : prev)),
104
+ []
105
+ )
106
+
107
+ const styles = selectStyles(themeTokens, maxWidth, viewport)
108
+
109
+ const childrenWithProps = React.Children.map(children, (child) => {
110
+ if (!React.isValidElement(child)) {
111
+ return child
112
+ }
113
+ return React.cloneElement(child, {
114
+ maxWidth: maxItemWidth,
115
+ registerWidth,
116
+ containerVariant: variant,
117
+ containerHideLabels: hideLabels,
118
+ containerIconVariant: iconVariant
119
+ })
120
+ })
121
+
122
+ return (
123
+ <View style={[staticStyles.wrapper, styles.wrapper]} ref={ref} {...selectProps(rest)}>
124
+ <View style={styles.scrollContainer}>
125
+ <HorizontalScroll
126
+ ScrollButton={HorizontalScrollButton}
127
+ itemPositions={itemPositions}
128
+ tokens={selectHorizontalScrollTokens(themeTokens)}
129
+ scrollButtonTokens={scrollButtonTokens}
130
+ variant={{ hideNavigationButtons: Platform.OS !== 'web' }}
131
+ >
132
+ <View style={[staticStyles.container, styles.container]}>{childrenWithProps}</View>
133
+ </HorizontalScroll>
134
+ </View>
135
+ </View>
136
+ )
137
+ }
138
+ )
139
+
140
+ Shortcuts.displayName = 'Shortcuts'
141
+
142
+ Shortcuts.propTypes = {
143
+ ...selectedSystemPropTypes,
144
+ tokens: getTokensPropType('Shortcuts'),
145
+ variant: variantProp.propType,
146
+ /**
147
+ * Custom tokens for `HorizontalScrollButton`
148
+ */
149
+ scrollButtonTokens: getTokensPropType('HorizontalScrollButton'),
150
+ /**
151
+ * Hide labels for all ShortcutsItem children. When true, labels are visually hidden but remain accessible to screen readers via the icon's accessibilityLabel.
152
+ */
153
+ hideLabels: PropTypes.bool,
154
+ /**
155
+ * Icon variant to apply to all ShortcutsItem children.
156
+ */
157
+ iconVariant: variantProp.propType,
158
+ /**
159
+ * ShortcutsItem components to be rendered within the Shortcuts container
160
+ */
161
+ children: PropTypes.node
162
+ }
163
+
164
+ const staticStyles = StyleSheet.create({
165
+ wrapper: {
166
+ flexGrow: 1
167
+ },
168
+ container: {
169
+ flexDirection: 'row',
170
+ flex: 1
171
+ }
172
+ })
173
+
174
+ export default Shortcuts
@@ -0,0 +1,297 @@
1
+ import React from 'react'
2
+ import { Image, Platform, Pressable, StyleSheet, View } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+
5
+ import { applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
6
+ import {
7
+ a11yProps,
8
+ clickProps,
9
+ getTokensPropType,
10
+ hrefAttrsProp,
11
+ linkProps,
12
+ resolvePressableState,
13
+ selectSystemProps,
14
+ variantProp,
15
+ viewProps,
16
+ wrapStringsInText
17
+ } from '../utils'
18
+ import Icon from '../Icon'
19
+
20
+ const DYNAMIC_WIDTH_VARIANT = 'dynamic'
21
+ const EQUAL_WIDTH_VARIANT = 'equal'
22
+
23
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, linkProps, viewProps])
24
+
25
+ const selectPressableStyles = (tokens, widthVariant, equalWidth) => {
26
+ const styles = {
27
+ borderColor: tokens.borderColor,
28
+ borderRadius: tokens.borderRadius,
29
+ borderWidth: tokens.borderWidth,
30
+ ...Platform.select({
31
+ web: {
32
+ outline: 'none'
33
+ }
34
+ })
35
+ }
36
+
37
+ if (widthVariant === DYNAMIC_WIDTH_VARIANT) {
38
+ styles.width = 'auto'
39
+ } else if (widthVariant === EQUAL_WIDTH_VARIANT) {
40
+ if (equalWidth) {
41
+ styles.width = equalWidth
42
+ } else {
43
+ styles.minWidth = tokens.width
44
+ }
45
+ } else {
46
+ styles.width = tokens.width
47
+ }
48
+
49
+ return styles
50
+ }
51
+
52
+ const selectIconContainerStyles = (tokens) => ({
53
+ paddingBottom: tokens.iconContainerPaddingBottom,
54
+ paddingLeft: tokens.iconContainerPaddingLeft,
55
+ paddingRight: tokens.iconContainerPaddingRight,
56
+ paddingTop: tokens.iconContainerPaddingTop
57
+ })
58
+
59
+ const selectIconVariant = () => ({
60
+ background: true,
61
+ padding: 'medium'
62
+ })
63
+
64
+ const selectIconTokens = (tokens) => ({
65
+ backgroundColor: tokens.iconBackgroundColor,
66
+ color: tokens.iconColor,
67
+ size: tokens.iconSize,
68
+ width: tokens.iconWidth
69
+ })
70
+
71
+ const selectImageStyles = (tokens) => ({
72
+ width: tokens.imageWidth,
73
+ height: tokens.imageHeight
74
+ })
75
+
76
+ const selectLabelContainerStyles = (tokens) => ({
77
+ paddingBottom: tokens.labelContainerPaddingBottom,
78
+ paddingLeft: tokens.labelContainerPaddingLeft,
79
+ paddingRight: tokens.labelContainerPaddingRight,
80
+ paddingTop: tokens.labelContainerPaddingTop
81
+ })
82
+
83
+ const selectTitleTextStyles = (tokens) =>
84
+ applyTextStyles({
85
+ fontColor: tokens.labelFontColor,
86
+ fontName: tokens.labelFontName,
87
+ fontSize: tokens.labelFontSize,
88
+ fontWeight: tokens.labelFontWeight,
89
+ lineHeight: tokens.labelLineHeight,
90
+ textDecorationLine: tokens.labelUnderline,
91
+ textAlign: tokens.labelTextAlign
92
+ })
93
+
94
+ /**
95
+ * A clickable shortcut item component that displays an icon or image with an optional label.
96
+ * Can be used within a Shortcuts container to create a grid of navigation shortcuts.
97
+ *
98
+ * @component
99
+ * @param {Object} props - Component props
100
+ * @param {string} [props.icon] - Icon identifier to display
101
+ * @param {Object} [props.image={ src: '', alt: '' }] - Image object with src and alt properties
102
+ * @param {string} [props.image.src] - Image source URL
103
+ * @param {string} [props.image.alt] - Image alt text for accessibility
104
+ * @param {string|React.ReactNode} props.label - Label text or content to display below the icon/image
105
+ * @param {boolean} [props.hideLabel=false] - Whether to hide the label for this specific item
106
+ * @param {string} [props.href] - Link URL for navigation
107
+ * @param {Object} [props.iconVariant] - Icon variant to apply to this specific item
108
+ * @param {Object} [props.tokens] - Theme tokens to customize appearance
109
+ * @param {Object} [props.variant] - Variant configuration object for this specific item
110
+ * @param {string} [props.variant.width] - Width variant (e.g., 'dynamic', 'equal')
111
+ * @param {Function} [props.onPressableStateChange] - Callback function that receives the pressable state object (pressed, hovered, focused)
112
+ * @param {number} [props.maxWidth] - Maximum width for equal width variant (injected by Shortcuts container)
113
+ * @param {Function} [props.registerWidth] - Callback to register width for equal width variant (injected by Shortcuts container)
114
+ * @param {Object} [props.containerVariant] - Variant configuration from Shortcuts container (injected by Shortcuts container)
115
+ * @param {boolean} [props.containerHideLabels] - Hide labels setting from Shortcuts container (injected by Shortcuts container)
116
+ * @param {Object} [props.containerIconVariant] - Icon variant from Shortcuts container (injected by Shortcuts container)
117
+ * @param {React.Ref} ref - Forwarded ref to the Pressable component
118
+ * @returns {React.ReactElement} The rendered shortcut item
119
+ *
120
+ * @example
121
+ * <ShortcutsItem
122
+ * icon={HomeIcon}
123
+ * label="Home"
124
+ * href="/home"
125
+ * onPressableStateChange={(state) => console.log(state)}
126
+ * />
127
+ */
128
+ const ShortcutsItem = React.forwardRef(
129
+ (
130
+ {
131
+ icon,
132
+ image = { src: '', alt: '' },
133
+ label,
134
+ hideLabel = false,
135
+ href,
136
+ iconVariant,
137
+ tokens,
138
+ variant,
139
+ onPressableStateChange,
140
+ maxWidth,
141
+ registerWidth,
142
+ containerVariant,
143
+ containerHideLabels,
144
+ containerIconVariant,
145
+ ...rest
146
+ },
147
+ ref
148
+ ) => {
149
+ const mergedVariant = { ...containerVariant, ...variant }
150
+ const widthVariant = mergedVariant?.width
151
+ const shouldHideLabel = hideLabel || containerHideLabels
152
+ const mergedIconVariant = iconVariant ?? containerIconVariant
153
+
154
+ const getThemeTokens = useThemeTokensCallback('ShortcutsItem', tokens, mergedVariant)
155
+ const getTokens = (pressableState) => getThemeTokens(resolvePressableState(pressableState))
156
+
157
+ const { onPress, ...props } = clickProps.toPressProps(rest)
158
+ const { hrefAttrs, rawRest } = hrefAttrsProp.bundle(props)
159
+ const selectedProps = selectProps({
160
+ href,
161
+ onPress: linkProps.handleHref({ href, onPress }),
162
+ hrefAttrs,
163
+ ...rawRest
164
+ })
165
+
166
+ const handleLayout = (event) => {
167
+ if (widthVariant === EQUAL_WIDTH_VARIANT && registerWidth) {
168
+ const { width } = event.nativeEvent.layout
169
+ registerWidth(width)
170
+ }
171
+ }
172
+
173
+ return (
174
+ <Pressable
175
+ ref={ref}
176
+ style={(pressableState) =>
177
+ selectPressableStyles(getTokens(pressableState), widthVariant, maxWidth)
178
+ }
179
+ onLayout={handleLayout}
180
+ {...selectedProps}
181
+ >
182
+ {(pressableState) => {
183
+ const themeTokens = getTokens(pressableState)
184
+
185
+ if (onPressableStateChange) {
186
+ onPressableStateChange(resolvePressableState(pressableState))
187
+ }
188
+
189
+ return (
190
+ <View style={staticStyles.container}>
191
+ {icon && (
192
+ <View style={selectIconContainerStyles(themeTokens)}>
193
+ <Icon
194
+ icon={icon}
195
+ variant={mergedIconVariant ?? selectIconVariant()}
196
+ tokens={mergedIconVariant ? {} : selectIconTokens(themeTokens)}
197
+ {...(Platform.OS === 'web' && { accessibilityLabel: label })}
198
+ />
199
+ </View>
200
+ )}
201
+ {!icon && image && (
202
+ <Image
203
+ source={image.src}
204
+ alt={image.alt}
205
+ style={selectImageStyles(themeTokens)}
206
+ resizeMethod="resize"
207
+ accessibilityIgnoresInvertColors
208
+ />
209
+ )}
210
+ {label && !shouldHideLabel && (
211
+ <View style={[staticStyles.label, selectLabelContainerStyles(themeTokens)]}>
212
+ {wrapStringsInText(label, { style: selectTitleTextStyles(themeTokens) })}
213
+ </View>
214
+ )}
215
+ </View>
216
+ )
217
+ }}
218
+ </Pressable>
219
+ )
220
+ }
221
+ )
222
+
223
+ ShortcutsItem.displayName = 'ShortcutsItem'
224
+
225
+ ShortcutsItem.propTypes = {
226
+ ...selectedSystemPropTypes,
227
+ tokens: getTokensPropType('ShortcutsItem'),
228
+ variant: variantProp.propType,
229
+ /**
230
+ * Icon for the ShortcutsItem
231
+ */
232
+ icon: PropTypes.elementType,
233
+ /**
234
+ * Image for the ShortcutsItem
235
+ */
236
+ image: PropTypes.shape({
237
+ src: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
238
+ alt: PropTypes.string
239
+ }),
240
+ /**
241
+ * Label for the ShortcutsItem
242
+ */
243
+ label: PropTypes.string,
244
+ /**
245
+ * Hide the label for this specific ShortcutsItem. When true, the label is visually hidden but remains accessible to screen readers via the icon's accessibilityLabel.
246
+ */
247
+ hideLabel: PropTypes.bool,
248
+ /**
249
+ * href for the ShortcutsItem
250
+ */
251
+ href: PropTypes.string,
252
+ /**
253
+ * Icon variant for this specific ShortcutsItem
254
+ */
255
+ iconVariant: variantProp.propType,
256
+ /**
257
+ * Callback function that receives the pressable state object containing pressed, hovered, and focused boolean properties
258
+ */
259
+ onPressableStateChange: PropTypes.func,
260
+ /**
261
+ * Maximum width for equal width variant (automatically injected by Shortcuts container)
262
+ * @private
263
+ */
264
+ maxWidth: PropTypes.number,
265
+ /**
266
+ * Callback to register width for equal width variant (automatically injected by Shortcuts container)
267
+ * @private
268
+ */
269
+ registerWidth: PropTypes.func,
270
+ /**
271
+ * Variant configuration from Shortcuts container (automatically injected by Shortcuts container)
272
+ * @private
273
+ */
274
+ containerVariant: variantProp.propType,
275
+ /**
276
+ * Hide labels setting from Shortcuts container (automatically injected by Shortcuts container)
277
+ * @private
278
+ */
279
+ containerHideLabels: PropTypes.bool,
280
+ /**
281
+ * Icon variant from Shortcuts container (automatically injected by Shortcuts container)
282
+ * @private
283
+ */
284
+ containerIconVariant: variantProp.propType
285
+ }
286
+
287
+ const staticStyles = StyleSheet.create({
288
+ container: {
289
+ alignItems: 'center',
290
+ justifyContent: 'center'
291
+ },
292
+ label: {
293
+ flexWrap: 'wrap'
294
+ }
295
+ })
296
+
297
+ export default ShortcutsItem
@@ -0,0 +1,4 @@
1
+ import Shortcuts from './Shortcuts'
2
+
3
+ export { default as ShortcutsItem } from './ShortcutsItem'
4
+ export default Shortcuts
@@ -263,6 +263,10 @@ const TextInputBase = React.forwardRef(
263
263
  // Add a space every 4 digits starting from the 5th position
264
264
  filteredText = formattedValue.replace(regex, '$1 ').trim()
265
265
  }
266
+ // Apply maxLength if provided
267
+ if (rest.maxLength && filteredText && filteredText.length > rest.maxLength) {
268
+ filteredText = filteredText.substring(0, rest.maxLength)
269
+ }
266
270
  setValue(filteredText, event)
267
271
  if (typeof onChangeText === 'function') onChangeText(filteredText, event)
268
272
  }
@@ -340,7 +344,7 @@ const TextInputBase = React.forwardRef(
340
344
  onMouseOut: handleMouseOut,
341
345
  onChange: handleChangeText,
342
346
  defaultValue: initialValue,
343
- maxLength: type === 'card' ? 19 : undefined,
347
+ maxLength: type === 'card' ? 19 : rest.maxLength,
344
348
  value: isControlled ? currentValue : undefined,
345
349
  onKeyPress
346
350
  }