@telus-uds/components-base 1.95.0 → 1.97.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 (67) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/lib/Autocomplete/Autocomplete.js +36 -2
  3. package/lib/Card/CardBase.js +4 -0
  4. package/lib/Carousel/Carousel.js +1 -1
  5. package/lib/Carousel/CarouselItem/CarouselItem.js +15 -19
  6. package/lib/InputLabel/InputLabel.js +2 -3
  7. package/lib/List/ListItemBase.js +3 -1
  8. package/lib/Listbox/GroupControl.js +4 -0
  9. package/lib/Modal/Modal.js +34 -13
  10. package/lib/Modal/ModalContent.js +4 -3
  11. package/lib/Modal/WebModal.js +1 -0
  12. package/lib/Notification/Notification.js +5 -5
  13. package/lib/Pagination/Pagination.js +9 -2
  14. package/lib/Pagination/constants.js +12 -0
  15. package/lib/Pagination/usePagination.js +13 -3
  16. package/lib/PriceLockup/PriceLockup.js +11 -3
  17. package/lib/Progress/Progress.js +5 -2
  18. package/lib/Progress/ProgressBar.js +4 -1
  19. package/lib/Tooltip/Tooltip.native.js +31 -3
  20. package/lib/Tooltip/shared.js +5 -0
  21. package/lib/Typography/Typography.js +42 -19
  22. package/lib/index.js +7 -0
  23. package/lib-module/Autocomplete/Autocomplete.js +36 -2
  24. package/lib-module/Card/CardBase.js +4 -0
  25. package/lib-module/Carousel/Carousel.js +1 -1
  26. package/lib-module/Carousel/CarouselItem/CarouselItem.js +15 -19
  27. package/lib-module/InputLabel/InputLabel.js +2 -3
  28. package/lib-module/List/ListItemBase.js +3 -1
  29. package/lib-module/Listbox/GroupControl.js +4 -0
  30. package/lib-module/Modal/Modal.js +34 -13
  31. package/lib-module/Modal/ModalContent.js +4 -3
  32. package/lib-module/Modal/WebModal.js +1 -0
  33. package/lib-module/Notification/Notification.js +5 -5
  34. package/lib-module/Pagination/Pagination.js +9 -2
  35. package/lib-module/Pagination/constants.js +3 -0
  36. package/lib-module/Pagination/usePagination.js +13 -3
  37. package/lib-module/PriceLockup/PriceLockup.js +11 -3
  38. package/lib-module/Progress/Progress.js +6 -3
  39. package/lib-module/Progress/ProgressBar.js +5 -2
  40. package/lib-module/Tooltip/Tooltip.native.js +31 -3
  41. package/lib-module/Tooltip/shared.js +5 -0
  42. package/lib-module/Typography/Typography.js +42 -19
  43. package/lib-module/index.js +1 -1
  44. package/package.json +2 -2
  45. package/src/Autocomplete/Autocomplete.jsx +41 -2
  46. package/src/Card/CardBase.jsx +6 -0
  47. package/src/Carousel/Carousel.jsx +1 -1
  48. package/src/Carousel/CarouselItem/CarouselItem.jsx +16 -22
  49. package/src/InputLabel/InputLabel.jsx +2 -3
  50. package/src/List/ListItemBase.jsx +6 -1
  51. package/src/Listbox/GroupControl.jsx +6 -1
  52. package/src/Modal/Modal.jsx +40 -14
  53. package/src/Modal/ModalContent.jsx +4 -3
  54. package/src/Modal/WebModal.jsx +1 -1
  55. package/src/Notification/Notification.jsx +5 -5
  56. package/src/Pagination/Pagination.jsx +9 -2
  57. package/src/Pagination/constants.js +3 -0
  58. package/src/Pagination/usePagination.js +14 -3
  59. package/src/PriceLockup/PriceLockup.jsx +9 -1
  60. package/src/Progress/Progress.jsx +6 -3
  61. package/src/Progress/ProgressBar.jsx +4 -2
  62. package/src/Tooltip/Tooltip.native.jsx +31 -3
  63. package/src/Tooltip/shared.js +5 -0
  64. package/src/Typography/Typography.jsx +37 -13
  65. package/src/index.js +2 -1
  66. package/types/FileUpload.d.ts +40 -0
  67. package/types/index.d.ts +3 -0
@@ -119,9 +119,10 @@ const selectArrowStyles = (_ref4, _ref5) => {
119
119
  };
120
120
  };
121
121
  const selectTextStyles = tokens => applyTextStyles(selectTokens('Typography', tokens));
122
- const defaultControl = (pressableState, variant) => /*#__PURE__*/_jsx(TooltipButton, {
122
+ const defaultControl = (pressableState, variant, tokens) => /*#__PURE__*/_jsx(TooltipButton, {
123
123
  pressableState: pressableState,
124
- variant: variant
124
+ variant: variant,
125
+ tokens: tokens
125
126
  });
126
127
 
127
128
  /**
@@ -150,6 +151,8 @@ const Tooltip = /*#__PURE__*/React.forwardRef((_ref6, ref) => {
150
151
  variant,
151
152
  inline = false,
152
153
  nativeID,
154
+ activateOnHover = false,
155
+ tooltipButtonTokens,
153
156
  ...rest
154
157
  } = _ref6;
155
158
  const [isOpen, setIsOpen] = React.useState(false);
@@ -250,6 +253,31 @@ const Tooltip = /*#__PURE__*/React.forwardRef((_ref6, ref) => {
250
253
  }
251
254
  setTooltipPosition(updatedPosition);
252
255
  }, [isOpen, position, tooltipDimensions, controlLayout, windowDimensions, arrowWidth, arrowOffset, tooltipPosition]);
256
+ React.useEffect(() => {
257
+ if (Platform.OS !== 'web') {
258
+ return undefined;
259
+ }
260
+ const handleHover = event => {
261
+ if (activateOnHover) {
262
+ const isInsideControl = controlRef.current && controlRef.current.contains(event.target);
263
+ if (isInsideControl) {
264
+ if (!isOpen) {
265
+ setIsOpen(!isOpen);
266
+ }
267
+ } else {
268
+ close();
269
+ }
270
+ }
271
+ };
272
+ document.addEventListener('mouseover', handleHover, {
273
+ capture: true
274
+ });
275
+ return () => {
276
+ document.removeEventListener('mouseover', handleHover, {
277
+ capture: true
278
+ });
279
+ };
280
+ }, [controlLayout, activateOnHover, isOpen, onPress]);
253
281
  const control = children !== undefined ? children : defaultControl;
254
282
  const pressableStyles = control === defaultControl ? Platform.select({
255
283
  web: {
@@ -278,7 +306,7 @@ const Tooltip = /*#__PURE__*/React.forwardRef((_ref6, ref) => {
278
306
  accessibilityLabel: getCopy('a11yText'),
279
307
  accessibilityRole: "button",
280
308
  nativeID: nativeID,
281
- children: typeof control === 'function' ? pressableState => control(getPressableState(pressableState), variant) : control
309
+ children: typeof control === 'function' ? pressableState => control(getPressableState(pressableState), variant, tooltipButtonTokens) : control
282
310
  }), isOpen && /*#__PURE__*/_jsx(Backdrop, {
283
311
  onPress: close,
284
312
  children: /*#__PURE__*/_jsxs(View, {
@@ -30,6 +30,11 @@ const propTypes = {
30
30
  * The `id` of the tooltip button.
31
31
  */
32
32
  nativeID: PropTypes.string,
33
+ /**
34
+ * Set to `true` to open the tooltip on hover
35
+ */
36
+ activateOnHover: PropTypes.bool,
37
+ tooltipButtonTokens: getTokensPropType('TooltipButton'),
33
38
  tokens: getTokensPropType('Tooltip'),
34
39
  variant: variantProp.propType
35
40
  };
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import Text from "react-native-web/dist/exports/Text";
4
4
  import View from "react-native-web/dist/exports/View";
5
+ import Platform from "react-native-web/dist/exports/Platform";
5
6
  import { useResponsiveThemeTokens, useTheme, useThemeTokens } from '../ThemeProvider';
6
7
  import { applyTextStyles } from '../ThemeProvider/utils';
7
8
  import { a11yProps, variantProp, getTokensPropType, getMaxFontMultiplier, headingTags, selectSystemProps, textTags, textProps, viewProps, getA11yPropsFromHtmlTag, StyleSheet, createMediaQueryStyles } from '../utils';
@@ -39,9 +40,22 @@ const selectTextStyles = (_ref, themeOptions) => {
39
40
  gradient
40
41
  });
41
42
  };
43
+ const HALF_FONT_SIZE = 2;
44
+ const QUARTER_FONT_SIZE = 4;
45
+ const selectMobileSubSupStyles = (_ref2, type) => {
46
+ let {
47
+ fontSize
48
+ } = _ref2;
49
+ return {
50
+ fontSize: fontSize / HALF_FONT_SIZE,
51
+ lineHeight: fontSize,
52
+ position: 'relative',
53
+ top: type === 'sub' ? fontSize / QUARTER_FONT_SIZE : -fontSize / QUARTER_FONT_SIZE
54
+ };
55
+ };
42
56
 
43
57
  // General-purpose flexible theme-neutral base component for text
44
- const Typography = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
58
+ const Typography = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
45
59
  let {
46
60
  children,
47
61
  variant,
@@ -54,7 +68,7 @@ const Typography = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
54
68
  dataSet,
55
69
  strikeThrough = false,
56
70
  ...rest
57
- } = _ref2;
71
+ } = _ref3;
58
72
  const viewport = useViewport();
59
73
  const {
60
74
  themeOptions
@@ -101,8 +115,8 @@ const Typography = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
101
115
  let textStyles;
102
116
  let mediaIds;
103
117
  if (enableMediaQueryStyleSheet) {
104
- const transformedThemeTokens = Object.entries(themeTokens).reduce((acc, _ref3) => {
105
- let [vp, viewportTokens] = _ref3;
118
+ const transformedThemeTokens = Object.entries(themeTokens).reduce((acc, _ref4) => {
119
+ let [vp, viewportTokens] = _ref4;
106
120
  acc[vp] = selectTextStyles({
107
121
  textAlign: align,
108
122
  textDecorationLine,
@@ -136,22 +150,31 @@ const Typography = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
136
150
  ...selectContainerProps(rest)
137
151
  };
138
152
  const resetTagStyling = child => {
139
- if (typeof child === 'object' && ((child === null || child === void 0 ? void 0 : child.type) === 'sub' || (child === null || child === void 0 ? void 0 : child.type) === 'sup')) {
140
- var _child$props;
141
- const childStyles = (child === null || child === void 0 ? void 0 : (_child$props = child.props) === null || _child$props === void 0 ? void 0 : _child$props.style) || {};
142
- const supFontSize = childStyles.fontSize ?? themeTokens.superScriptFontSize;
143
- const sanitizedChild = /*#__PURE__*/React.cloneElement(child, {
144
- style: {
145
- ...childStyles,
146
- ...(supFontSize ? {
147
- fontSize: supFontSize
148
- } : {}),
149
- lineHeight: 0
150
- }
151
- });
152
- return sanitizedChild;
153
+ var _child$props;
154
+ if (typeof child !== 'object' || !((child === null || child === void 0 ? void 0 : child.type) === 'sub' || (child === null || child === void 0 ? void 0 : child.type) === 'sup')) {
155
+ return child;
153
156
  }
154
- return child;
157
+ const childStyles = (child === null || child === void 0 ? void 0 : (_child$props = child.props) === null || _child$props === void 0 ? void 0 : _child$props.style) || {};
158
+ const supFontSize = childStyles.fontSize ?? themeTokens.superScriptFontSize;
159
+ const isMobile = Platform.OS === 'ios' || Platform.OS === 'android';
160
+ const isSubSup = (child === null || child === void 0 ? void 0 : child.type) === 'sub' || (child === null || child === void 0 ? void 0 : child.type) === 'sup';
161
+ const mobileStyles = isMobile && isSubSup ? selectMobileSubSupStyles(themeTokens, child === null || child === void 0 ? void 0 : child.type) : {};
162
+ const defaultStyles = !isMobile && isSubSup ? {
163
+ fontSize: supFontSize,
164
+ lineHeight: 0
165
+ } : {};
166
+ const sanitizedChild = /*#__PURE__*/React.cloneElement(isMobile && isSubSup ? /*#__PURE__*/_jsx(View, {
167
+ children: /*#__PURE__*/_jsx(Text, {
168
+ style: [childStyles, mobileStyles],
169
+ children: child.props.children
170
+ })
171
+ }) : child, {
172
+ style: {
173
+ ...childStyles,
174
+ ...defaultStyles
175
+ }
176
+ });
177
+ return sanitizedChild;
155
178
  };
156
179
  const sanitizeChildren = () => {
157
180
  if (Array.isArray(children)) {
@@ -71,6 +71,6 @@ export { default as BaseProvider } from './BaseProvider';
71
71
  export { useHydrationContext } from './BaseProvider/HydrationContext';
72
72
  export { default as Validator } from './Validator';
73
73
  export { default as ViewportProvider, useViewport, ViewportContext } from './ViewportProvider';
74
- export { default as ThemeProvider, useTheme, useSetTheme, useThemeTokens, useThemeTokensCallback, getThemeTokens, applyOuterBorder, applyTextStyles, applyShadowToken } from './ThemeProvider';
74
+ export { default as ThemeProvider, useTheme, useSetTheme, useThemeTokens, useThemeTokensCallback, getThemeTokens, applyOuterBorder, applyTextStyles, applyShadowToken, useResponsiveThemeTokens } from './ThemeProvider';
75
75
  export * from './utils';
76
76
  export { default as Portal } from './Portal';
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "@floating-ui/react-native": "^0.8.1",
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@telus-uds/system-constants": "^1.3.0",
14
- "@telus-uds/system-theme-tokens": "^2.64.0",
14
+ "@telus-uds/system-theme-tokens": "^2.66.0",
15
15
  "airbnb-prop-types": "^2.16.0",
16
16
  "css-mediaquery": "^0.1.2",
17
17
  "expo-linear-gradient": "^12.5.0",
@@ -86,6 +86,6 @@
86
86
  "standard-engine": {
87
87
  "skip": true
88
88
  },
89
- "version": "1.95.0",
89
+ "version": "1.97.0",
90
90
  "types": "types/index.d.ts"
91
91
  }
@@ -135,6 +135,8 @@ const Autocomplete = React.forwardRef(
135
135
  // When it's nested, selected value
136
136
  const [nestedSelectedValue, setNestedSelectedValue] = React.useState(null)
137
137
 
138
+ const [isInputVisible, setIsInputVisible] = React.useState(true)
139
+
138
140
  const { supportsProps, ...selectedProps } = selectProps(rest)
139
141
  const { hint, label: inputLabel } = supportsProps
140
142
  const hintExpansionEnabled = isFocused && helpText && !currentValue
@@ -244,6 +246,32 @@ const Autocomplete = React.forwardRef(
244
246
  }
245
247
  }, [nestedSelectedValue, items])
246
248
 
249
+ React.useEffect(() => {
250
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
251
+ return undefined
252
+ }
253
+
254
+ const observer = new IntersectionObserver((entries) => {
255
+ const [entry] = entries
256
+ setIsInputVisible(entry.isIntersecting)
257
+ if (!entry.isIntersecting) {
258
+ setIsExpanded(false)
259
+ }
260
+ })
261
+
262
+ const currentInputRef = inputRef.current
263
+
264
+ if (currentInputRef) {
265
+ observer.observe(currentInputRef)
266
+ }
267
+
268
+ return () => {
269
+ if (currentInputRef) {
270
+ observer.unobserve(currentInputRef)
271
+ }
272
+ }
273
+ }, [inputRef])
274
+
247
275
  const handleClose = (event) => {
248
276
  if (
249
277
  (event.type === 'keydown' && (event.key === 'Escape' || event.key === '27')) ||
@@ -313,7 +341,18 @@ const Autocomplete = React.forwardRef(
313
341
  readOnly={readOnly}
314
342
  ref={inputRef}
315
343
  {...(Platform.OS !== 'web'
316
- ? { onLayout: (event) => setSourceLayout(event.nativeEvent.layout) }
344
+ ? {
345
+ onLayout: (event) => {
346
+ setSourceLayout(event.nativeEvent.layout)
347
+ const { y, height } = event.nativeEvent.layout
348
+ if (y >= 0 && height > 0) {
349
+ setIsInputVisible(true)
350
+ } else {
351
+ setIsInputVisible(false)
352
+ setIsExpanded(false)
353
+ }
354
+ }
355
+ }
317
356
  : {})}
318
357
  tokens={inputTokens}
319
358
  validation={validation}
@@ -324,7 +363,7 @@ const Autocomplete = React.forwardRef(
324
363
  )
325
364
  }}
326
365
  </InputSupports>
327
- {(isExpanded || hintExpansionEnabled) && (
366
+ {(isExpanded || hintExpansionEnabled) && isInputVisible && (
328
367
  <>
329
368
  <Listbox.Overlay
330
369
  overlaidPosition={overlaidPosition}
@@ -21,6 +21,7 @@ const selectStyles = ({
21
21
  paddingTop,
22
22
  minWidth,
23
23
  shadow,
24
+ backgroundGradient,
24
25
  gradient
25
26
  }) => {
26
27
  return {
@@ -42,6 +43,11 @@ const selectStyles = ({
42
43
  boxShadow: `inset 0 1000px white`,
43
44
  border: `${borderWidth}px solid transparent`
44
45
  }
46
+ : {}),
47
+ ...(backgroundGradient && Platform.OS === 'web'
48
+ ? {
49
+ backgroundImage: `linear-gradient(${backgroundGradient.angle}deg, ${backgroundGradient.stops[0].color}, ${backgroundGradient.stops[1].color})`
50
+ }
45
51
  : {})
46
52
  }
47
53
  }
@@ -120,7 +120,7 @@ const selectPreviousNextNavigationButtonStyles = (
120
120
  }
121
121
 
122
122
  const getPeekingProps = (viewport) => {
123
- if (viewport === 'xs' || viewport === 'sm') {
123
+ if (viewport === 'xs' || viewport === 'sm' || viewport === 'md') {
124
124
  return {
125
125
  peekingFirstSpace: 48,
126
126
  peekingGap: 16,
@@ -20,43 +20,37 @@ const selectContainerStyle = ({
20
20
  totalItems,
21
21
  enablePeeking,
22
22
  peekingMarginLeft,
23
- peekingMarginRight,
24
23
  peekingFirstSpace,
25
24
  peekingLastSpace,
26
25
  peekingMiddleSpace,
27
26
  peekingGap,
28
27
  hidden
29
28
  }) => {
30
- const isFirst = elementIndex === 0
31
- const isLast = elementIndex === totalItems - 1
32
- const isMiddle = !isFirst && !isLast
33
- const isActive = activeIndex === elementIndex
34
-
35
29
  let adjustedWidth = width
36
30
  let marginLeft = 0
37
- let marginRight = 0
31
+ const marginRight = 0
38
32
 
39
33
  if (enablePeeking) {
40
- if (isActive) {
41
- adjustedWidth = width - (peekingMarginLeft + peekingGap + peekingFirstSpace)
42
- } else if (isMiddle) {
43
- adjustedWidth = width - peekingGap
44
- }
34
+ adjustedWidth = width - (peekingMarginLeft + peekingGap + peekingFirstSpace)
35
+ const isFirst = elementIndex === 0
36
+ const isActive = activeIndex === elementIndex
45
37
 
46
- if (isFirst) {
47
- if (isActive) {
38
+ if (isActive) {
39
+ if (isFirst) {
48
40
  marginLeft = peekingMarginLeft
49
- } else if (activeIndex === totalItems - 1) {
50
- marginLeft = peekingLastSpace
51
41
  } else {
52
- marginLeft = peekingMiddleSpace
42
+ marginLeft = peekingGap
43
+ }
44
+ } else if (isFirst) {
45
+ marginLeft = peekingMarginLeft + peekingGap + peekingFirstSpace + peekingMiddleSpace
46
+ if (activeIndex > 1) {
47
+ marginLeft += (peekingGap + peekingMiddleSpace * 2) * (activeIndex - 1)
48
+ }
49
+ if (activeIndex === totalItems - 1) {
50
+ marginLeft += peekingLastSpace - peekingMiddleSpace
53
51
  }
54
- }
55
-
56
- if (isLast && isActive) {
57
- marginRight = peekingMarginRight
58
52
  } else {
59
- marginRight = peekingGap
53
+ marginLeft = peekingGap
60
54
  }
61
55
  }
62
56
 
@@ -146,8 +146,7 @@ export default InputLabel
146
146
  const staticStyles = StyleSheet.create({
147
147
  container: {
148
148
  flexShrink: 1,
149
- flexDirection: 'row',
150
- alignItems: 'baseline'
149
+ flexDirection: 'row'
151
150
  },
152
151
  label: {
153
152
  flexShrink: 1
@@ -157,7 +156,7 @@ const staticStyles = StyleSheet.create({
157
156
  flexShrink: 0
158
157
  },
159
158
  tooltipAlign: {
160
- alignSelf: 'flex-start',
159
+ alignSelf: 'center',
161
160
  justifyContent: 'center'
162
161
  }
163
162
  })
@@ -107,7 +107,12 @@ const staticStyles = StyleSheet.create({
107
107
  flex: 1,
108
108
  flexDirection: 'row'
109
109
  },
110
- titleAndContentContainer: { flexDirection: 'column', flexShrink: 1 }
110
+ titleAndContentContainer: {
111
+ flexDirection: 'column',
112
+ flexShrink: 1,
113
+ flexGrow: 1,
114
+ textAlign: 'justify'
115
+ }
111
116
  })
112
117
 
113
118
  ListItemBase.propTypes = {
@@ -48,6 +48,11 @@ const GroupControl = React.forwardRef(({ expanded, pressed, hover, focus, label,
48
48
  itemOutline,
49
49
  groupHeight
50
50
  } = tokens
51
+
52
+ const getTextStyles = () => ({
53
+ color: groupColor
54
+ })
55
+
51
56
  return (
52
57
  <View
53
58
  onPress={() => setSelectedId(id)}
@@ -71,7 +76,7 @@ const GroupControl = React.forwardRef(({ expanded, pressed, hover, focus, label,
71
76
  ]}
72
77
  ref={ref}
73
78
  >
74
- <Text>{label}</Text>
79
+ <Text style={getTextStyles()}>{label}</Text>
75
80
  <Spacer space={1} direction="row" />
76
81
  <Icon
77
82
  icon={tokens.groupIcon}
@@ -121,6 +121,8 @@ const Modal = React.forwardRef(
121
121
  const viewport = useViewport()
122
122
  const themeTokens = useThemeTokens('Modal', tokens, variant, { viewport, maxWidth })
123
123
  const modalRef = useScrollBlocking(isOpen)
124
+ const modalBodyRef = React.useRef(ref)
125
+ const modalContentRef = React.useRef(null)
124
126
 
125
127
  const { closeIcon: CloseIconComponent } = themeTokens
126
128
 
@@ -135,6 +137,32 @@ const Modal = React.forwardRef(
135
137
  if (event.key === 'Escape') onClose()
136
138
  }
137
139
 
140
+ const manageFocus = React.useCallback(
141
+ (event) => {
142
+ if (event.key === 'Tab') {
143
+ const focusableElements = Array.from(
144
+ modalBodyRef?.current?.querySelectorAll(`
145
+ a[href], button, textarea, input, select,
146
+ [tabindex]:not([tabindex="-1"]),
147
+ [contenteditable="true"]
148
+ `)
149
+ )
150
+
151
+ const firstElement = focusableElements[0]
152
+ const lastElement = focusableElements[focusableElements.length - 1]
153
+
154
+ if (event.shiftKey && document.activeElement === firstElement) {
155
+ event.preventDefault()
156
+ lastElement.focus()
157
+ } else if (!event.shiftKey && document.activeElement === lastElement) {
158
+ event.preventDefault()
159
+ firstElement.focus()
160
+ }
161
+ }
162
+ },
163
+ [modalBodyRef]
164
+ )
165
+
138
166
  // Show the custom react node passed to `closedButton` or the default close button if `closeButton` is `undefined`.
139
167
  // Hide the close button if `closeButton` is `null`.
140
168
  const showCloseButton = closeButton !== null
@@ -145,22 +173,20 @@ const Modal = React.forwardRef(
145
173
 
146
174
  React.useEffect(() => {
147
175
  if (Platform.OS === 'web') {
148
- const handleFocus = () => {
149
- // If the focus is on the last item of the web modal container, move it to the close button
150
- if (document.activeElement === focusTrapRef.current && closeButtonRef.current) {
151
- closeButtonRef.current.focus()
152
- }
153
- return undefined
154
- }
155
-
156
- // Add an event listener to manage focus in the web modal container
157
- document.addEventListener('focusin', handleFocus)
176
+ // Add an event listener to manage keydown and focus in the web modal container
177
+ document.addEventListener('keydown', manageFocus)
158
178
 
159
179
  // Clean up the event listener
160
- return () => document.removeEventListener('focusin', handleFocus)
180
+ return () => document.removeEventListener('keydown', manageFocus)
161
181
  }
162
182
  return undefined
163
- }, [])
183
+ }, [manageFocus])
184
+
185
+ React.useEffect(() => {
186
+ if (isOpen) {
187
+ modalContentRef?.current?.focus()
188
+ }
189
+ }, [isOpen, modalContentRef?.current?.focus])
164
190
 
165
191
  if (!isOpen) {
166
192
  return null
@@ -174,7 +200,7 @@ const Modal = React.forwardRef(
174
200
  pointerEvents="box-none" // don't capture backdrop press events
175
201
  >
176
202
  <View
177
- ref={ref}
203
+ ref={modalBodyRef}
178
204
  style={[staticStyles.modal, selectModalStyles(themeTokens)]}
179
205
  onKeyUp={handleKeyUp}
180
206
  >
@@ -230,7 +256,7 @@ const Modal = React.forwardRef(
230
256
 
231
257
  if (Platform.OS === 'web') {
232
258
  return (
233
- <WebModal {...selectProps(rest)}>
259
+ <WebModal {...selectProps(rest)} ref={modalContentRef}>
234
260
  {content}
235
261
  <View accessibilityRole="button" ref={focusTrapRef} />
236
262
  </WebModal>
@@ -60,6 +60,7 @@ const ModalContent = React.forwardRef(
60
60
  borderColor,
61
61
  gap,
62
62
  direction,
63
+ footerTopWidth,
63
64
  hasBorder
64
65
  }) => ({
65
66
  flexDirection: direction,
@@ -70,8 +71,8 @@ const ModalContent = React.forwardRef(
70
71
  paddingLeft,
71
72
  paddingRight,
72
73
  paddingTop,
73
- borderTopColor: hasBorder ? borderColor : 'transparent',
74
- borderTopWidth: hasBorder ? 1 : 0
74
+ borderTopWidth: footerTopWidth > 0 ? footerTopWidth : (hasBorder && 1) || 0,
75
+ borderTopColor: borderColor
75
76
  })
76
77
 
77
78
  const headingStyles = {
@@ -155,7 +156,7 @@ const styles = StyleSheet.create({
155
156
  minHeight: Platform.OS === 'web' ? '100%' : 'auto'
156
157
  },
157
158
  cancelButton: {
158
- flex: 1
159
+ display: 'flex'
159
160
  }
160
161
  })
161
162
 
@@ -17,7 +17,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
17
17
  const WebModal = React.forwardRef(({ children, ...rest }, ref) => {
18
18
  return (
19
19
  <Portal>
20
- <View style={staticStyles.container} {...selectProps(rest)} ref={ref}>
20
+ <View style={staticStyles.container} {...selectProps(rest)} focusable={true} ref={ref}>
21
21
  <div style={staticStyles.content} role="dialog" aria-modal={true}>
22
22
  {children}
23
23
  </div>
@@ -102,11 +102,11 @@ const getMediaQueryStyles = (
102
102
  flexShrink: 1,
103
103
  justifyContent: 'space-between',
104
104
  ...createMediaQueryStyles({
105
- xs: { width: themeOptions?.contentMaxWidth?.xs || '100%' },
106
- md: { width: themeOptions?.contentMaxWidth?.md || '100%' },
107
- lg: { width: themeOptions?.contentMaxWidth?.lg || '100%' },
108
- sm: { width: themeOptions?.contentMaxWidth?.sm || '100%' },
109
- xl: { width: themeOptions?.contentMaxWidth?.xl || '100%' }
105
+ xs: { maxWidth: themeOptions?.contentMaxWidth?.xs || '100%' },
106
+ md: { maxWidth: themeOptions?.contentMaxWidth?.md || '100%' },
107
+ lg: { maxWidth: themeOptions?.contentMaxWidth?.lg || '100%' },
108
+ sm: { maxWidth: themeOptions?.contentMaxWidth?.sm || '100%' },
109
+ xl: { maxWidth: themeOptions?.contentMaxWidth?.xl || '100%' }
110
110
  })
111
111
  }
112
112
  })
@@ -1,4 +1,5 @@
1
1
  import React from 'react'
2
+ import PropTypes from 'prop-types'
2
3
  import { View, Text, StyleSheet } from 'react-native'
3
4
 
4
5
  import {
@@ -42,6 +43,7 @@ const Pagination = React.forwardRef(
42
43
  sideButtonTokens,
43
44
  LinkRouter,
44
45
  linkRouterProps,
46
+ numberOfPagesBeforeEllipsis = 4,
45
47
  ...rest
46
48
  },
47
49
  ref
@@ -80,7 +82,8 @@ const Pagination = React.forwardRef(
80
82
  shouldRenderEllipsis
81
83
  } = usePagination({
82
84
  items,
83
- truncateAbove
85
+ truncateAbove,
86
+ numberOfPagesBeforeEllipsis
84
87
  })
85
88
 
86
89
  const ellipsisTextStyles = selectTextStyles(themeTokens, themeOptions)
@@ -220,7 +223,11 @@ Pagination.propTypes = {
220
223
  /**
221
224
  * Custom tokens for `PaginationSideButton`
222
225
  */
223
- sideButtonTokens: getTokensPropType('PaginationSideButton')
226
+ sideButtonTokens: getTokensPropType('PaginationSideButton'),
227
+ /**
228
+ * The number of pages before the ellipsis is shown
229
+ */
230
+ numberOfPagesBeforeEllipsis: PropTypes.number
224
231
  }
225
232
 
226
233
  const staticStyles = StyleSheet.create({
@@ -0,0 +1,3 @@
1
+ export const TRUNCATE_ABOVE_VALUE = 4
2
+ export const MIN_PAGES_TO_SHOW = 3
3
+ export const DEFAULT_PAGE_TO_SHOW_VALUE = 1
@@ -1,11 +1,13 @@
1
+ import { DEFAULT_PAGE_TO_SHOW_VALUE, MIN_PAGES_TO_SHOW, TRUNCATE_ABOVE_VALUE } from './constants'
1
2
  /**
2
3
  * Handles configurable truncating of pagination items.
3
4
  *
4
5
  * @param {object} props
5
6
  * @param {React.Element[]} items
6
7
  * @param {number} truncateAbove
8
+ * @param {number} numberOfPagesBeforeEllipsis
7
9
  */
8
- function usePagination({ items, truncateAbove }) {
10
+ function usePagination({ items, truncateAbove, numberOfPagesBeforeEllipsis }) {
9
11
  const activeItemIndex = Math.max(
10
12
  items.findIndex((item) => item.props.isActive),
11
13
  0 // default to the first item if none is marked as active
@@ -16,8 +18,17 @@ function usePagination({ items, truncateAbove }) {
16
18
  return { onPress, href, hrefAttrs, variant, tokens }
17
19
  }
18
20
 
19
- const windowSize = truncateAbove > 4 ? 3 : 1
20
- const truncateEnabled = items.length > truncateAbove
21
+ let windowSize
22
+
23
+ if (numberOfPagesBeforeEllipsis >= 1 && numberOfPagesBeforeEllipsis < items.length) {
24
+ windowSize = numberOfPagesBeforeEllipsis
25
+ } else if (truncateAbove > TRUNCATE_ABOVE_VALUE) {
26
+ windowSize = MIN_PAGES_TO_SHOW
27
+ } else {
28
+ windowSize = DEFAULT_PAGE_TO_SHOW_VALUE
29
+ }
30
+
31
+ const truncateEnabled = items.length > windowSize
21
32
 
22
33
  const truncateWindowStart = windowSize
23
34
  const truncateWindowEnd = items.length - 1 - windowSize
@@ -61,6 +61,10 @@ const selectBottomTextTypographyTokens = ({ bottomTextFontSize, bottomTextLineHe
61
61
  lineHeight: bottomTextLineHeight
62
62
  })
63
63
 
64
+ const selectContainertokens = ({ alignItemsText }) => ({
65
+ alignItems: alignItemsText
66
+ })
67
+
64
68
  const PriceLockup = React.forwardRef(
65
69
  (
66
70
  {
@@ -106,7 +110,11 @@ const PriceLockup = React.forwardRef(
106
110
  const bottomTextTypographyTokens = selectBottomTextTypographyTokens(themeTokens)
107
111
 
108
112
  return (
109
- <View style={[staticStyles.priceLockupContainer]} ref={ref} {...selectProps(rest)}>
113
+ <View
114
+ style={[staticStyles.priceLockupContainer, selectContainertokens(themeTokens)]}
115
+ ref={ref}
116
+ {...selectProps(rest)}
117
+ >
110
118
  {topText ? (
111
119
  <View style={staticStyles.topText}>
112
120
  {renderTypography(topText, topTextTypographyTokens)}