@telus-uds/components-base 3.27.0 → 3.28.1

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 (48) hide show
  1. package/CHANGELOG.md +30 -5
  2. package/lib/cjs/Card/CardBase.js +12 -3
  3. package/lib/cjs/Carousel/Carousel.js +16 -15
  4. package/lib/cjs/Carousel/CarouselItem/CarouselItem.js +3 -3
  5. package/lib/cjs/ExpandCollapse/Control.js +5 -1
  6. package/lib/cjs/ExpandCollapse/ExpandCollapse.js +17 -8
  7. package/lib/cjs/ExpandCollapse/Panel.js +7 -2
  8. package/lib/cjs/IconButton/IconButton.js +10 -5
  9. package/lib/cjs/Modal/Modal.js +21 -11
  10. package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +5 -1
  11. package/lib/cjs/Progress/Progress.js +19 -5
  12. package/lib/cjs/Progress/ProgressBar.js +22 -4
  13. package/lib/cjs/Progress/ProgressContext.js +11 -0
  14. package/lib/cjs/SideNav/Item.js +3 -3
  15. package/lib/cjs/SideNav/ItemsGroup.js +46 -19
  16. package/lib/cjs/SideNav/SideNav.js +29 -13
  17. package/lib/esm/Card/CardBase.js +12 -3
  18. package/lib/esm/Carousel/Carousel.js +16 -15
  19. package/lib/esm/Carousel/CarouselItem/CarouselItem.js +3 -3
  20. package/lib/esm/ExpandCollapse/Control.js +5 -1
  21. package/lib/esm/ExpandCollapse/ExpandCollapse.js +17 -8
  22. package/lib/esm/ExpandCollapse/Panel.js +7 -2
  23. package/lib/esm/IconButton/IconButton.js +10 -5
  24. package/lib/esm/Modal/Modal.js +21 -11
  25. package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +5 -1
  26. package/lib/esm/Progress/Progress.js +19 -5
  27. package/lib/esm/Progress/ProgressBar.js +22 -4
  28. package/lib/esm/Progress/ProgressContext.js +5 -0
  29. package/lib/esm/SideNav/Item.js +3 -3
  30. package/lib/esm/SideNav/ItemsGroup.js +45 -20
  31. package/lib/esm/SideNav/SideNav.js +29 -13
  32. package/lib/package.json +2 -2
  33. package/package.json +2 -2
  34. package/src/Card/CardBase.jsx +9 -3
  35. package/src/Carousel/Carousel.jsx +16 -15
  36. package/src/Carousel/CarouselItem/CarouselItem.jsx +3 -3
  37. package/src/ExpandCollapse/Control.jsx +1 -1
  38. package/src/ExpandCollapse/ExpandCollapse.jsx +9 -8
  39. package/src/ExpandCollapse/Panel.jsx +10 -2
  40. package/src/IconButton/IconButton.jsx +40 -28
  41. package/src/Modal/Modal.jsx +23 -11
  42. package/src/MultiSelectFilter/MultiSelectFilter.jsx +6 -1
  43. package/src/Progress/Progress.jsx +18 -7
  44. package/src/Progress/ProgressBar.jsx +19 -14
  45. package/src/Progress/ProgressContext.js +5 -0
  46. package/src/SideNav/Item.jsx +3 -3
  47. package/src/SideNav/ItemsGroup.jsx +36 -16
  48. package/src/SideNav/SideNav.jsx +22 -8
@@ -4,6 +4,19 @@ import ItemContent from './ItemContent';
4
4
  import ExpandCollapse from '../ExpandCollapse';
5
5
  import { getTokensPropType, variantProp, componentPropType, selectTokens } from '../utils';
6
6
  import { useThemeTokensCallback } from '../ThemeProvider';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ const selectPanelTokens = _ref => {
9
+ let {
10
+ borderWidth,
11
+ borderColor,
12
+ backgroundColor
13
+ } = _ref;
14
+ return {
15
+ contentPanelBackgroundColor: backgroundColor,
16
+ borderBottomWidth: borderWidth,
17
+ borderColor
18
+ };
19
+ };
7
20
 
8
21
  /**
9
22
  Expandable content areas for use within `SideNav`.
@@ -15,8 +28,7 @@ import { useThemeTokensCallback } from '../ThemeProvider';
15
28
  ## Usage Criteria
16
29
  - Use `SideNav.ItemsGroup` with large pages that have multiple sections
17
30
  */
18
- import { jsx as _jsx } from "react/jsx-runtime";
19
- const ItemsGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
31
+ const ItemsGroup = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
20
32
  let {
21
33
  children,
22
34
  label,
@@ -27,7 +39,7 @@ const ItemsGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
27
39
  tokens,
28
40
  itemTokens,
29
41
  onToggle
30
- } = _ref;
42
+ } = _ref2;
31
43
  // A SideNav control uses the same style and theme as SideNavItem, with a 'parent' variant,
32
44
  // plus control-specific tokens from the SideNavItemsGroup theme (e.g. open/close icon, etc).
33
45
  const getAppearance = appearance => ({
@@ -40,28 +52,27 @@ const ItemsGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
40
52
  type: 'parent'
41
53
  });
42
54
  const getGroupTokens = useThemeTokensCallback('SideNavItemsGroup', tokens, variant);
43
- const getPanelTokens = appearance => {
44
- const {
45
- panelBorderColor,
46
- ...itemsGroupTokens
47
- } = getGroupTokens(getAppearance(appearance));
48
- const groupTokens = {
49
- ...itemsGroupTokens,
50
- borderWidth: 0,
51
- marginBottom: 0
52
- };
53
- return selectTokens('ExpandCollapsePanel', groupTokens);
54
- };
55
55
  const getItemTokens = useThemeTokensCallback('SideNavItem', itemTokens, variant);
56
- const getControlTokens = appearance => selectTokens('ExpandCollapseControl', {
57
- ...getItemTokens(getItemAppearance(appearance)),
58
- // main style from SideNavItem
59
- ...getGroupTokens(getAppearance(appearance)) // control-specific tokens like icon etc
56
+ const getPanelTokens = appearance => selectTokens('ExpandCollapsePanel', {
57
+ ...staticTokens.panel,
58
+ ...getGroupTokens(getAppearance(appearance)),
59
+ ...selectPanelTokens(getItemTokens(getItemAppearance(appearance)))
60
+ });
61
+ const getControlTokens = appearance => ({
62
+ ...selectTokens('ExpandCollapseControl', {
63
+ ...getItemTokens(getItemAppearance(appearance)),
64
+ // main style from SideNavItem
65
+ ...getGroupTokens(getAppearance(appearance)) // control-specific tokens like icon etc,
66
+ }),
67
+ ...staticTokens.control
60
68
  });
61
69
  const controlContent = controlState => {
62
70
  const currentItemTokens = getItemTokens(getItemAppearance(controlState));
63
71
  return /*#__PURE__*/_jsx(ItemContent, {
64
- tokens: currentItemTokens,
72
+ tokens: {
73
+ ...currentItemTokens,
74
+ ...staticTokens.content
75
+ },
65
76
  children: label
66
77
  });
67
78
  };
@@ -77,9 +88,23 @@ const ItemsGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
77
88
  active: isActive
78
89
  } // ExpandCollapse.Panel handles expanded state
79
90
  ,
91
+ disableMobileScrollBuffer: true,
80
92
  children: children
81
93
  });
82
94
  });
95
+ const staticTokens = {
96
+ panel: {
97
+ borderWidth: 0,
98
+ marginBottom: 0
99
+ },
100
+ control: {
101
+ borderWidth: 0,
102
+ textLine: null
103
+ },
104
+ content: {
105
+ accentWidth: 0
106
+ }
107
+ };
83
108
  ItemsGroup.displayName = 'ItemsGroup';
84
109
  ItemsGroup.propTypes = {
85
110
  /**
@@ -6,13 +6,28 @@ import ItemsGroup from './ItemsGroup';
6
6
  import { useThemeTokens } from '../ThemeProvider';
7
7
  import { a11yProps, componentPropType, getTokensPropType, selectSystemProps, variantProp, viewProps } from '../utils';
8
8
  import { jsx as _jsx } from "react/jsx-runtime";
9
- function selectBorderStyles(tokens) {
9
+ const selectContainerTokens = _ref => {
10
+ let {
11
+ borderWidth,
12
+ borderStyle,
13
+ borderColor
14
+ } = _ref;
10
15
  return {
11
- borderBottomWidth: tokens.borderWidth,
12
- borderBottomStyle: tokens.borderStyle,
13
- borderBottomColor: tokens.borderColor
16
+ borderTopWidth: borderWidth,
17
+ borderStyle,
18
+ borderColor
14
19
  };
15
- }
20
+ };
21
+ const selectItemTokens = function () {
22
+ let tokens = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
23
+ let isLastItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
24
+ return {
25
+ ...tokens,
26
+ ...(isLastItem ? {
27
+ borderWidth: 0
28
+ } : {})
29
+ };
30
+ };
16
31
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
17
32
 
18
33
  /**
@@ -20,7 +35,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
20
35
  - Use in conjunction with a large amount of educational / informational content
21
36
  - Allow the user to navigate between options frequently and efficiently
22
37
  */
23
- const SideNav = /*#__PURE__*/React.forwardRef((_ref, ref) => {
38
+ const SideNav = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
24
39
  let {
25
40
  children,
26
41
  variant = {},
@@ -29,7 +44,7 @@ const SideNav = /*#__PURE__*/React.forwardRef((_ref, ref) => {
29
44
  itemTokens,
30
45
  groupTokens,
31
46
  ...rest
32
- } = _ref;
47
+ } = _ref2;
33
48
  const themeTokens = useThemeTokens('SideNav', tokens, variant);
34
49
  const [active, setActive] = React.useState({
35
50
  groupId: undefined,
@@ -47,14 +62,15 @@ const SideNav = /*#__PURE__*/React.forwardRef((_ref, ref) => {
47
62
  return /*#__PURE__*/_jsx(ExpandCollapse, {
48
63
  ref: ref,
49
64
  maxOpen: accordion ? 1 : null,
50
- style: selectBorderStyles(themeTokens),
65
+ tokens: selectContainerTokens(themeTokens),
51
66
  ...selectProps(rest),
52
- children: _ref2 => {
67
+ children: _ref3 => {
53
68
  let {
54
69
  openIds,
55
70
  onToggle
56
- } = _ref2;
57
- const renderItem = (item, index, groupId) => {
71
+ } = _ref3;
72
+ const renderItem = function (item, index, groupId) {
73
+ let isLastItem = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
58
74
  const {
59
75
  itemId = `item-${index}`,
60
76
  onPress
@@ -72,7 +88,7 @@ const SideNav = /*#__PURE__*/React.forwardRef((_ref, ref) => {
72
88
  ...variant,
73
89
  type: 'child'
74
90
  } : variant,
75
- tokens: itemTokens,
91
+ tokens: selectItemTokens(itemTokens, isLastItem),
76
92
  isActive: isItemActive(itemId, groupId),
77
93
  onPress: handlePress
78
94
  });
@@ -96,7 +112,7 @@ const SideNav = /*#__PURE__*/React.forwardRef((_ref, ref) => {
96
112
  openGroups: openIds,
97
113
  isActive: isGroupActive,
98
114
  onToggle: handleToggle
99
- }, React.Children.map(child.props.children, (item, itemIndex) => renderItem(item, itemIndex, groupId)));
115
+ }, React.Children.map(child.props.children, (item, itemIndex) => renderItem(item, itemIndex, groupId, itemIndex === child.props.children.length - 1)));
100
116
  }
101
117
  return null;
102
118
  });
package/lib/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@react-native-picker/picker": "^2.9.0",
14
14
  "@telus-uds/system-constants": "^3.0.0",
15
- "@telus-uds/system-theme-tokens": "^4.19.0",
15
+ "@telus-uds/system-theme-tokens": "^4.20.0",
16
16
  "airbnb-prop-types": "^2.16.0",
17
17
  "css-mediaquery": "^0.1.2",
18
18
  "expo-document-picker": "^13.0.1",
@@ -84,6 +84,6 @@
84
84
  "standard-engine": {
85
85
  "skip": true
86
86
  },
87
- "version": "3.27.0",
87
+ "version": "3.28.1",
88
88
  "types": "types/index.d.ts"
89
89
  }
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@react-native-picker/picker": "^2.9.0",
14
14
  "@telus-uds/system-constants": "^3.0.0",
15
- "@telus-uds/system-theme-tokens": "^4.19.0",
15
+ "@telus-uds/system-theme-tokens": "^4.20.0",
16
16
  "airbnb-prop-types": "^2.16.0",
17
17
  "css-mediaquery": "^0.1.2",
18
18
  "expo-document-picker": "^13.0.1",
@@ -84,6 +84,6 @@
84
84
  "standard-engine": {
85
85
  "skip": true
86
86
  },
87
- "version": "3.27.0",
87
+ "version": "3.28.1",
88
88
  "types": "types/index.d.ts"
89
89
  }
@@ -233,7 +233,13 @@ export const selectStyles = ({
233
233
  */
234
234
  const CardBase = React.forwardRef(
235
235
  ({ children, tokens, dataSet, backgroundImage, fullBleedContent, cardState, ...rest }, ref) => {
236
- const cardStyle = selectStyles(typeof tokens === 'function' ? tokens(cardState) : tokens)
236
+ const resolvedTokens = typeof tokens === 'function' ? tokens(cardState) : tokens
237
+ const tokensToUse =
238
+ backgroundImage && backgroundImage.src
239
+ ? { ...resolvedTokens, gradient: undefined, backgroundGradient: undefined }
240
+ : resolvedTokens
241
+
242
+ const cardStyle = selectStyles(tokensToUse)
237
243
  const props = selectProps(rest)
238
244
 
239
245
  let content = children
@@ -314,7 +320,7 @@ const CardBase = React.forwardRef(
314
320
  })
315
321
 
316
322
  return (
317
- <View style={containerStyle} dataSet={dataSet} ref={ref} {...props}>
323
+ <View style={{ ...containerStyle, borderRadius }} dataSet={dataSet} ref={ref} {...props}>
318
324
  {content}
319
325
  </View>
320
326
  )
@@ -393,7 +399,7 @@ const staticStyles = StyleSheet.create({
393
399
  contentOverlay: {
394
400
  position: 'relative',
395
401
  width: '100%',
396
- height: '100%',
402
+ minHeight: '100%',
397
403
  zIndex: 2
398
404
  },
399
405
  containContainer: {
@@ -125,7 +125,7 @@ const selectControlButtonPositionStyles = ({
125
125
  isAutoPlayEnabled,
126
126
  viewport,
127
127
  maxWidth,
128
- viewportWidth
128
+ containerWidth
129
129
  }) => {
130
130
  const styles = {}
131
131
 
@@ -148,13 +148,11 @@ const selectControlButtonPositionStyles = ({
148
148
  }
149
149
 
150
150
  if (enablePeeking) {
151
- if (positionProperty === POSITION_PROPERTIES.RIGHT) {
152
- const rightMargin = (viewportWidth - maxWidth) / 2
153
- positionOffset += rightMargin
154
- } else if (positionProperty === POSITION_PROPERTIES.LEFT) {
155
- const leftMargin = (viewportWidth - maxWidth) / 2
156
- positionOffset += leftMargin
157
- }
151
+ const { peekingMiddleSpace, peekingGap } = getPeekingProps(viewport)
152
+ const clampedMaxWidth = Math.min(maxWidth || containerWidth, containerWidth)
153
+ const slideRightEdge = (containerWidth + clampedMaxWidth) / 2 - peekingMiddleSpace
154
+ const buttonCenter = slideRightEdge + peekingGap / 2
155
+ positionOffset = containerWidth - buttonCenter - buttonWidth / 2
158
156
  }
159
157
 
160
158
  styles[positionProperty] = positionOffset
@@ -173,7 +171,7 @@ const selectPreviousNextNavigationButtonStyles = (
173
171
  isAutoPlayEnabled,
174
172
  viewport,
175
173
  maxWidth,
176
- viewportWidth
174
+ containerWidth
177
175
  ) => {
178
176
  const styles = {
179
177
  zIndex: 1,
@@ -199,7 +197,7 @@ const selectPreviousNextNavigationButtonStyles = (
199
197
  isAutoPlayEnabled,
200
198
  viewport,
201
199
  maxWidth,
202
- viewportWidth
200
+ containerWidth
203
201
  })
204
202
  }
205
203
  }
@@ -343,7 +341,7 @@ const calculateFinalWidth = (containerWidth, enablePeeking, viewport, maxWidth)
343
341
 
344
342
  if (enablePeeking) {
345
343
  const { peekingGap, peekingMiddleSpace } = getPeekingProps(viewport)
346
- const baseWidth = maxWidth || containerWidth
344
+ const baseWidth = Math.min(maxWidth || containerWidth, containerWidth)
347
345
  finalWidth = baseWidth - peekingMiddleSpace * PEEKING_MULTIPLIER + peekingGap
348
346
  }
349
347
 
@@ -572,7 +570,10 @@ const Carousel = React.forwardRef(
572
570
  const { peekingGap, peekingMiddleSpace } = getPeekingProps(viewport)
573
571
 
574
572
  let finalWidth
575
- const baseWidth = maxWidth || containerLayoutRef.current.width
573
+ const baseWidth = Math.min(
574
+ maxWidth || containerLayoutRef.current.width,
575
+ containerLayoutRef.current.width
576
+ )
576
577
  const slideWide = baseWidth - peekingMiddleSpace * PEEKING_MULTIPLIER
577
578
 
578
579
  if (activeIndexRef.current === 0) {
@@ -1177,7 +1178,7 @@ const Carousel = React.forwardRef(
1177
1178
  isAutoPlayEnabled,
1178
1179
  viewport,
1179
1180
  maxWidth,
1180
- viewportWidth: currentViewportWidth
1181
+ containerWidth: containerLayout.width
1181
1182
  })
1182
1183
  ]}
1183
1184
  >
@@ -1205,7 +1206,7 @@ const Carousel = React.forwardRef(
1205
1206
  isAutoPlayEnabled,
1206
1207
  viewport,
1207
1208
  maxWidth,
1208
- currentViewportWidth
1209
+ containerLayout.width
1209
1210
  )}
1210
1211
  testID="previous-button-container"
1211
1212
  >
@@ -1303,7 +1304,7 @@ const Carousel = React.forwardRef(
1303
1304
  isAutoPlayEnabled,
1304
1305
  viewport,
1305
1306
  maxWidth,
1306
- currentViewportWidth
1307
+ containerLayout.width
1307
1308
  )}
1308
1309
  testID="next-button-container"
1309
1310
  >
@@ -31,10 +31,10 @@ const selectContainerStyle = ({
31
31
 
32
32
  if (enablePeeking) {
33
33
  const isFirst = elementIndex === 0
34
- const baseWidth = maxWidth || width
35
- adjustedWidth = baseWidth - peekingMiddleSpace * 2
34
+ const clampedMaxWidth = Math.min(maxWidth || width, width)
35
+ adjustedWidth = clampedMaxWidth - peekingMiddleSpace * 2
36
36
  if (isFirst) {
37
- marginLeft = peekingMiddleSpace + (viewportWidth - maxWidth) / 2
37
+ marginLeft = peekingMiddleSpace + (viewportWidth - clampedMaxWidth) / 2
38
38
  } else {
39
39
  marginLeft = peekingGap
40
40
  }
@@ -50,7 +50,7 @@ function selectIconContainerStyles({ iconGap, iconPaddingTop, iconPosition }) {
50
50
  const paddingSide = iconPosition === 'right' ? 'paddingLeft' : 'paddingRight'
51
51
  return {
52
52
  [paddingSide]: iconGap,
53
- paddingTop: iconPaddingTop
53
+ ...(iconPaddingTop && { transform: [{ translateY: iconPaddingTop }] })
54
54
  }
55
55
  }
56
56
 
@@ -20,13 +20,14 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
20
20
  contentfulProps
21
21
  ])
22
22
 
23
- function selectBorderStyles(tokens) {
24
- return {
25
- borderBottomWidth: tokens.borderWidth,
26
- borderBottomStyle: tokens.borderStyle,
27
- borderBottomColor: tokens.borderColor
28
- }
29
- }
23
+ const selectWrapperStyles = ({ borderWidth, borderTopWidth, borderStyle, borderColor }) => ({
24
+ borderStyle,
25
+ borderColor,
26
+ borderTopWidth,
27
+ borderBottomWidth: borderWidth,
28
+ borderBottomStyle: borderStyle,
29
+ borderBottomColor: borderColor
30
+ })
30
31
 
31
32
  /**
32
33
  * Flexible base component for lists where some or all items are collapsible headers.
@@ -55,7 +56,7 @@ const ExpandCollapse = React.forwardRef(
55
56
 
56
57
  return (
57
58
  <View style={staticStyles.container} ref={ref} {...selectProps(rest)} dataSet={dataSet}>
58
- <View style={selectBorderStyles(themeTokens)}>
59
+ <View style={selectWrapperStyles(themeTokens)}>
59
60
  {typeof children === 'function'
60
61
  ? children({ openIds, onToggle, resetValues, setValues, unsetValues, instanceId })
61
62
  : children}
@@ -96,6 +96,7 @@ const ExpandCollapsePanel = React.forwardRef(
96
96
  controlRef,
97
97
  content,
98
98
  copy = 'en',
99
+ disableMobileScrollBuffer = false,
99
100
  ...rest
100
101
  },
101
102
  ref
@@ -118,7 +119,10 @@ const ExpandCollapsePanel = React.forwardRef(
118
119
 
119
120
  // on mobile devices we require a scroll buffer equal to the font size
120
121
  // to avoid triggering scrolling unnecessarily
121
- const mobileScrollBuffer = Platform.OS === 'web' ? 0 : selectTextStyles(themeTokens)?.fontSize
122
+ const mobileScrollBuffer =
123
+ Platform.OS === 'web' || disableMobileScrollBuffer
124
+ ? 0
125
+ : selectTextStyles(themeTokens)?.fontSize
122
126
 
123
127
  const handleControlPress = (event) => {
124
128
  onToggle?.(panelId, event)
@@ -287,7 +291,11 @@ ExpandCollapsePanel.propTypes = {
287
291
  /**
288
292
  * A boolean prop to determine if the panel is a content panel or not. If true, the panel will not have a control
289
293
  */
290
- content: PropTypes.bool
294
+ content: PropTypes.bool,
295
+ /**
296
+ * A boolean prop to disable the extra scroll buffer on mobile devices (only applicable on iOS/Android, ignored on web)
297
+ */
298
+ disableMobileScrollBuffer: PropTypes.bool
291
299
  }
292
300
 
293
301
  export default ExpandCollapsePanel
@@ -92,34 +92,46 @@ const selectInnerStyle = (
92
92
  height
93
93
  },
94
94
  password
95
- ) => ({
96
- // Inner borders animate with the icon and should be treated like a themable feature of the icon
97
- borderColor,
98
- borderRadius,
99
- borderWidth,
100
- borderTopLeftRadius,
101
- borderTopRightRadius,
102
- borderBottomLeftRadius,
103
- borderBottomRightRadius,
104
- borderTopWidth,
105
- borderRightWidth,
106
- borderBottomWidth,
107
- borderLeftWidth,
108
- padding: calculatePadding(padding, borderWidth),
109
- paddingLeft: calculatePadding(paddingLeft, borderLeftWidth),
110
- paddingRight: calculatePadding(paddingRight, borderRightWidth),
111
- paddingTop: calculatePadding(paddingTop, borderTopWidth),
112
- paddingBottom: calculatePadding(paddingBottom, borderBottomWidth),
113
- ...Platform.select({
114
- web: {
115
- pointerEvents: 'none',
116
- display: 'inline-flex',
117
- alignItems: 'center',
118
- justifyContent: 'center'
119
- }
120
- }),
121
- ...getPasswordDimensions(password, width, height)
122
- })
95
+ ) => {
96
+ const basePadding = calculatePadding(padding, borderWidth)
97
+
98
+ const calculateSpecificPadding = (specificPadding, specificBorderWidth) => {
99
+ const calculated = calculatePadding(
100
+ specificPadding ?? padding,
101
+ specificBorderWidth ?? borderWidth
102
+ )
103
+ return calculated !== basePadding && calculated !== undefined ? calculated : undefined
104
+ }
105
+
106
+ return {
107
+ // Inner borders animate with the icon and should be treated like a themable feature of the icon
108
+ borderColor,
109
+ borderRadius,
110
+ borderWidth,
111
+ borderTopLeftRadius,
112
+ borderTopRightRadius,
113
+ borderBottomLeftRadius,
114
+ borderBottomRightRadius,
115
+ borderTopWidth,
116
+ borderRightWidth,
117
+ borderBottomWidth,
118
+ borderLeftWidth,
119
+ padding: basePadding,
120
+ paddingLeft: calculateSpecificPadding(paddingLeft, borderLeftWidth),
121
+ paddingRight: calculateSpecificPadding(paddingRight, borderRightWidth),
122
+ paddingTop: calculateSpecificPadding(paddingTop, borderTopWidth),
123
+ paddingBottom: calculateSpecificPadding(paddingBottom, borderBottomWidth),
124
+ ...Platform.select({
125
+ web: {
126
+ pointerEvents: 'none',
127
+ display: 'inline-flex',
128
+ alignItems: 'center',
129
+ justifyContent: 'center'
130
+ }
131
+ }),
132
+ ...getPasswordDimensions(password, width, height)
133
+ }
134
+ }
123
135
 
124
136
  /**
125
137
  * A pressable themeless base component that handles pressable states and passes tokens
@@ -68,9 +68,10 @@ const selectModalStyles = ({
68
68
  ...applyShadowToken(shadow)
69
69
  })
70
70
 
71
- const selectBackdropStyles = ({ backdropColor, backdropOpacity }) => ({
71
+ const selectBackdropStyles = ({ backdropColor, backdropOpacity, backdropCursor }) => ({
72
72
  backgroundColor: backdropColor,
73
- opacity: backdropOpacity
73
+ opacity: backdropOpacity,
74
+ ...(Platform.OS === 'web' && backdropCursor ? { cursor: backdropCursor } : {})
74
75
  })
75
76
 
76
77
  const selectCloseButtonContainerStyles = ({ paddingRight, paddingTop }) => ({
@@ -119,12 +120,20 @@ const Modal = React.forwardRef(
119
120
  cancelButtonText,
120
121
  cancelButtonType,
121
122
  footer,
123
+ backgroundDismissible = true,
122
124
  ...rest
123
125
  },
124
126
  ref
125
127
  ) => {
126
128
  const viewport = useViewport()
127
- const themeTokens = useThemeTokens('Modal', tokens, variant, { viewport, maxWidth })
129
+
130
+ const isBackdropClickable = onClose && backgroundDismissible
131
+
132
+ const themeTokens = useThemeTokens('Modal', tokens, variant, {
133
+ viewport,
134
+ maxWidth,
135
+ backdropCursor: isBackdropClickable ? 'pointer' : 'default'
136
+ })
128
137
  const modalRef = useScrollBlocking(isOpen)
129
138
  const modalBodyRef = React.useRef(ref)
130
139
  const modalContentRef = React.useRef(null)
@@ -253,7 +262,7 @@ const Modal = React.forwardRef(
253
262
  </View>
254
263
  {/* when a modal becomes open its first focusable element is being automatically focused */}
255
264
  {/* and we prefer the close button over backdrop */}
256
- <TouchableWithoutFeedback onPress={handleClose}>
265
+ <TouchableWithoutFeedback onPress={isBackdropClickable && handleClose}>
257
266
  <View style={[staticStyles.backdrop, selectBackdropStyles(themeTokens)]} />
258
267
  </TouchableWithoutFeedback>
259
268
  </ScrollView>
@@ -349,7 +358,15 @@ Modal.propTypes = {
349
358
  /**
350
359
  * Receive a react node or an array of nodes to render at the bottom of the modal, above the action buttons.
351
360
  */
352
- footer: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)])
361
+ footer: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
362
+ /**
363
+ * Controls whether the modal can be dismissed by clicking on the backdrop.
364
+ * When set to `false`, clicking the backdrop will not close the modal.
365
+ * The backdrop cursor automatically changes to 'default' to indicate it's not clickable.
366
+ * Note: Backdrop dismissal requires `onClose` to be defined.
367
+ * @default true
368
+ */
369
+ backgroundDismissible: PropTypes.bool
353
370
  }
354
371
 
355
372
  export default Modal
@@ -361,12 +378,7 @@ const staticStyles = StyleSheet.create({
361
378
  left: 0,
362
379
  right: 0,
363
380
  bottom: 0,
364
- zIndex: -1,
365
- ...Platform.select({
366
- web: {
367
- cursor: 'pointer'
368
- }
369
- })
381
+ zIndex: -1
370
382
  },
371
383
  positioningContainer: {
372
384
  flexBasis: '100%',
@@ -185,6 +185,12 @@ const MultiSelectFilter = React.forwardRef(
185
185
 
186
186
  React.useEffect(() => setCheckedIds(currentValues ?? []), [currentValues])
187
187
 
188
+ React.useEffect(() => {
189
+ if (isOpen && onOpen) {
190
+ onOpen()
191
+ }
192
+ }, [isOpen, onOpen])
193
+
188
194
  const uniqueFields = ['id', 'label']
189
195
  if (!containUniqueFields(items, uniqueFields)) {
190
196
  throw new Error(`MultiSelectFilter items must have unique ${uniqueFields.join(', ')}`)
@@ -194,7 +200,6 @@ const MultiSelectFilter = React.forwardRef(
194
200
 
195
201
  const handleChange = (event) => {
196
202
  if (pressHandlers.onPress) pressHandlers?.onPress(event)
197
- if (isOpen) onOpen()
198
203
  setIsOpen(!isOpen)
199
204
  }
200
205
 
@@ -4,6 +4,7 @@ import { View, StyleSheet } from 'react-native'
4
4
 
5
5
  import { applyShadowToken, useThemeTokens } from '../ThemeProvider'
6
6
  import { a11yProps, getTokensPropType, selectSystemProps, variantProp, viewProps } from '../utils'
7
+ import ProgressContext from './ProgressContext'
7
8
 
8
9
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
9
10
 
@@ -45,6 +46,12 @@ const selectProgressStyles = ({
45
46
  *
46
47
  * - Use the `size` variant to control the height of your progress bars: passing `'mini'` will make your
47
48
  * progress bar container narrower.
49
+ * - Use the `layers` variant to control how multiple progress bars are positioned:
50
+ * - `false` (default): bars are positioned vertically one below the other.
51
+ * - `true`: bars overlay on top of each other (layered/stacked on z-axis).
52
+ * Note: The `layers` prop is deprecated. After August 2026, `layers: true` will become the permanent
53
+ * default behavior and the `layers` prop will be removed. To maintain vertical layout after removal,
54
+ * use separate individual Progress components.
48
55
  *
49
56
  * ## Usability and A11y guidelines
50
57
  *
@@ -55,15 +62,19 @@ const selectProgressStyles = ({
55
62
  */
56
63
  const Progress = React.forwardRef(({ children, tokens, variant, ...rest }, ref) => {
57
64
  const themeTokens = useThemeTokens('Progress', tokens, variant)
65
+ // Default to false (vertical layout) to preserve existing behavior and avoid breaking changes
66
+ const layers = variant?.layers ?? false
58
67
 
59
68
  return (
60
- <View
61
- ref={ref}
62
- style={[staticStyles.progressContainer, selectProgressStyles(themeTokens)]}
63
- {...selectProps(rest)}
64
- >
65
- {children}
66
- </View>
69
+ <ProgressContext.Provider value={{ layers }}>
70
+ <View
71
+ ref={ref}
72
+ style={[staticStyles.progressContainer, selectProgressStyles(themeTokens)]}
73
+ {...selectProps(rest)}
74
+ >
75
+ {children}
76
+ </View>
77
+ </ProgressContext.Provider>
67
78
  )
68
79
  })
69
80
  Progress.displayName = 'Progress'